summaryrefslogtreecommitdiff
path: root/src/nuke/OCIODisplay/OCIODisplay.cpp
diff options
context:
space:
mode:
authorMatteo F. Vescovi <mfv.debian@gmail.com>2013-08-20 09:53:19 +0100
committerMatteo F. Vescovi <mfv.debian@gmail.com>2013-08-20 09:53:19 +0100
commit66e5d9e2915733247bca47d077414ec2594aedad (patch)
treef4070a31bf015e159dadd34378cda703d8f6edea /src/nuke/OCIODisplay/OCIODisplay.cpp
opencolorio (1.0.8~dfsg0-2) unstable; urgency=low
* debian/rules: get-orig-source stuff added * debian/rules: useless dh addon removed * debian/rules: License.txt duplicate removed * debian/rules: SSE optimization disabled (Closes: #719174) * debian/libopencolorio1.symbols: file removed (Closes: #719175) # imported from the archive
Diffstat (limited to 'src/nuke/OCIODisplay/OCIODisplay.cpp')
-rw-r--r--src/nuke/OCIODisplay/OCIODisplay.cpp561
1 files changed, 561 insertions, 0 deletions
diff --git a/src/nuke/OCIODisplay/OCIODisplay.cpp b/src/nuke/OCIODisplay/OCIODisplay.cpp
new file mode 100644
index 0000000..bf82ab8
--- /dev/null
+++ b/src/nuke/OCIODisplay/OCIODisplay.cpp
@@ -0,0 +1,561 @@
+/**
+ * OpenColorIO Display Iop.
+ */
+
+#include "OCIODisplay.h"
+
+namespace OCIO = OCIO_NAMESPACE;
+
+#include <algorithm>
+#include <string>
+#include <sstream>
+#include <stdexcept>
+
+#include <DDImage/Channel.h>
+#include <DDImage/PixelIop.h>
+#include <DDImage/NukeWrapper.h>
+#include <DDImage/Row.h>
+#include <DDImage/Knobs.h>
+#include <DDImage/Enumeration_KnobI.h>
+#include <DDImage/ddImageVersionNumbers.h>
+
+// Should we use cascasing ColorSpace menus
+#if defined kDDImageVersionInteger && (kDDImageVersionInteger>=62300)
+#define OCIO_CASCADE
+#endif
+
+OCIODisplay::OCIODisplay(Node *n) : DD::Image::PixelIop(n)
+{
+ m_layersToProcess = DD::Image::Mask_RGBA;
+ m_hasLists = false;
+ m_colorSpaceIndex = m_displayIndex = m_viewIndex = 0;
+ m_displayKnob = m_viewKnob = NULL;
+ m_gain = 1.0;
+ m_gamma = 1.0;
+ m_channel = 2;
+ m_transform = OCIO::DisplayTransform::Create();
+
+ try
+ {
+ OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
+
+ OCIO::ConstColorSpaceRcPtr defaultcs = config->getColorSpace(OCIO::ROLE_SCENE_LINEAR);
+ if(!defaultcs) throw std::runtime_error("ROLE_SCENE_LINEAR not defined.");
+ std::string defaultColorSpaceName = defaultcs->getName();
+
+ for(int i=0; i<config->getNumColorSpaces(); ++i)
+ {
+ std::string csname = config->getColorSpaceNameByIndex(i);
+
+#ifdef OCIO_CASCADE
+ std::string family = config->getColorSpace(csname.c_str())->getFamily();
+ if(family.empty())
+ m_colorSpaceNames.push_back(csname.c_str());
+ else
+ m_colorSpaceNames.push_back(family + "/" + csname);
+#else
+ m_colorSpaceNames.push_back(csname);
+#endif
+
+ if(defaultColorSpaceName == csname)
+ {
+ m_colorSpaceIndex = i;
+ }
+ }
+
+ std::string defaultDisplay = config->getDefaultDisplay();
+
+ for(int i=0; i<config->getNumDisplays(); ++i)
+ {
+ std::string display = config->getDisplay(i);
+ m_displayNames.push_back(display);
+
+ if(display == defaultDisplay)
+ {
+ m_displayIndex = i;
+ }
+ }
+ }
+ catch(OCIO::Exception& e)
+ {
+ std::cerr << "OCIODisplay: " << e.what() << std::endl;
+ }
+ catch(...)
+ {
+ std::cerr << "OCIODisplay: Unknown exception during OCIO setup." << std::endl;
+ }
+
+ // Build the cstr vectors on our second pass
+ for(unsigned int i=0; i<m_colorSpaceNames.size(); ++i)
+ {
+ m_colorSpaceCstrNames.push_back(m_colorSpaceNames[i].c_str());
+ }
+ m_colorSpaceCstrNames.push_back(NULL);
+
+ for(unsigned int i=0; i<m_displayNames.size(); ++i)
+ {
+ m_displayCstrNames.push_back(m_displayNames[i].c_str());
+ }
+ m_displayCstrNames.push_back(NULL);
+
+ refreshDisplayTransforms();
+
+ m_hasLists = !(m_colorSpaceNames.empty() || m_displayNames.empty() || m_viewNames.empty());
+
+ if(!m_hasLists)
+ {
+ std::cerr << "OCIODisplay: Missing one or more of colorspaces, display devices, or display transforms." << std::endl;
+ }
+}
+
+OCIODisplay::~OCIODisplay()
+{
+
+}
+
+void OCIODisplay::knobs(DD::Image::Knob_Callback f)
+{
+#ifdef OCIO_CASCADE
+ DD::Image::CascadingEnumeration_knob(f,
+ &m_colorSpaceIndex, &m_colorSpaceCstrNames[0], "colorspace", "input colorspace");
+#else
+ DD::Image::Enumeration_knob(f,
+ &m_colorSpaceIndex, &m_colorSpaceCstrNames[0], "colorspace", "input colorspace");
+#endif
+ DD::Image::SetFlags(f, DD::Image::Knob::ALWAYS_SAVE);
+ DD::Image::Tooltip(f, "Input data is taken to be in this colorspace.");
+
+ m_displayKnob = DD::Image::Enumeration_knob(f,
+ &m_displayIndex, &m_displayCstrNames[0], "display", "display device");
+ DD::Image::SetFlags(f, DD::Image::Knob::ALWAYS_SAVE);
+ DD::Image::Tooltip(f, "Display device for output.");
+
+ m_viewKnob = DD::Image::Enumeration_knob(f,
+ &m_viewIndex, &m_viewCstrNames[0], "view", "view transform");
+ DD::Image::SetFlags(f, DD::Image::Knob::ALWAYS_SAVE);
+ DD::Image::Tooltip(f, "Display transform for output.");
+
+ DD::Image::Float_knob(f, &m_gain, DD::Image::IRange(1.0 / 64.0f, 64.0f), "gain");
+ DD::Image::SetFlags(f, DD::Image::Knob::NO_ANIMATION | DD::Image::Knob::NO_UNDO | DD::Image::Knob::LOG_SLIDER);
+ DD::Image::Tooltip(f, "Exposure adjustment, in scene-linear, prior to the display transform.");
+
+ DD::Image::Float_knob(f, &m_gamma, DD::Image::IRange(0, 4), "gamma");
+ DD::Image::SetFlags(f, DD::Image::Knob::NO_ANIMATION | DD::Image::Knob::NO_UNDO | DD::Image::Knob::LOG_SLIDER);
+ DD::Image::Tooltip(f, "Gamma correction applied after the display transform.");
+
+ static const char* const channelvalues[] = {
+ "Luminance",
+ "Matte overlay",
+ "RGB",
+ "R",
+ "G",
+ "B",
+ "A",
+ 0
+ };
+ DD::Image::Enumeration_knob(f, &m_channel, channelvalues, "channel_selector", "channel view");
+ DD::Image::SetFlags(f, DD::Image::Knob::NO_ANIMATION | DD::Image::Knob::NO_UNDO);
+ DD::Image::Tooltip(f, "Specify which channels to view (prior to the display transform).");
+
+ DD::Image::Divider(f);
+
+ DD::Image::Input_ChannelSet_knob(f, &m_layersToProcess, 0, "layer", "layer");
+ // DD::Image::SetFlags(f, DD::Image::Knob::NO_CHECKMARKS | DD::Image::Knob::NO_ALPHA_PULLDOWN);
+ DD::Image::Tooltip(f, "Set which layer to process. This should be a layer with rgb data.");
+
+ DD::Image::Tab_knob(f, "Context");
+ {
+ DD::Image::String_knob(f, &m_contextKey1, "key1");
+ DD::Image::Spacer(f, 10);
+ DD::Image::String_knob(f, &m_contextValue1, "value1");
+ DD::Image::ClearFlags(f, DD::Image::Knob::STARTLINE);
+
+ DD::Image::String_knob(f, &m_contextKey2, "key2");
+ DD::Image::Spacer(f, 10);
+ DD::Image::String_knob(f, &m_contextValue2, "value2");
+ DD::Image::ClearFlags(f, DD::Image::Knob::STARTLINE);
+
+ DD::Image::String_knob(f, &m_contextKey3, "key3");
+ DD::Image::Spacer(f, 10);
+ DD::Image::String_knob(f, &m_contextValue3, "value3");
+ DD::Image::ClearFlags(f, DD::Image::Knob::STARTLINE);
+
+ DD::Image::String_knob(f, &m_contextKey4, "key4");
+ DD::Image::Spacer(f, 10);
+ DD::Image::String_knob(f, &m_contextValue4, "value4");
+ DD::Image::ClearFlags(f, DD::Image::Knob::STARTLINE);
+ }
+}
+
+OCIO::ConstContextRcPtr OCIODisplay::getLocalContext()
+{
+ OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
+ OCIO::ConstContextRcPtr context = config->getCurrentContext();
+ OCIO::ContextRcPtr mutableContext;
+
+ if(!m_contextKey1.empty())
+ {
+ if(!mutableContext) mutableContext = context->createEditableCopy();
+ mutableContext->setStringVar(m_contextKey1.c_str(), m_contextValue1.c_str());
+ }
+ if(!m_contextKey2.empty())
+ {
+ if(!mutableContext) mutableContext = context->createEditableCopy();
+ mutableContext->setStringVar(m_contextKey2.c_str(), m_contextValue2.c_str());
+ }
+ if(!m_contextKey3.empty())
+ {
+ if(!mutableContext) mutableContext = context->createEditableCopy();
+ mutableContext->setStringVar(m_contextKey3.c_str(), m_contextValue3.c_str());
+ }
+ if(!m_contextKey4.empty())
+ {
+ if(!mutableContext) mutableContext = context->createEditableCopy();
+ mutableContext->setStringVar(m_contextKey4.c_str(), m_contextValue4.c_str());
+ }
+
+ if(mutableContext) context = mutableContext;
+
+ return context;
+}
+
+void OCIODisplay::append(DD::Image::Hash& localhash)
+{
+ // TODO: Hang onto the context, what if getting it
+ // (and querying getCacheID) is expensive?
+ try
+ {
+ OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
+ OCIO::ConstContextRcPtr context = getLocalContext();
+ std::string configCacheID = config->getCacheID(context);
+ localhash.append(configCacheID);
+
+ // This is required due to our custom channel overlay mode post-processing
+ localhash.append(m_channel);
+ }
+ catch(OCIO::Exception &e)
+ {
+ error(e.what());
+ return;
+ }
+}
+
+void OCIODisplay::_validate(bool for_real)
+{
+ input0().validate(for_real);
+
+ if(!m_hasLists)
+ {
+ error("Missing one or more of colorspaces, display devices, or display transforms.");
+ return;
+ }
+
+ int nColorSpaces = static_cast<int>(m_colorSpaceNames.size());
+ if(m_colorSpaceIndex < 0 || m_colorSpaceIndex >= nColorSpaces)
+ {
+ std::ostringstream err;
+ err << "ColorSpace index (" << m_colorSpaceIndex << ") out of range.";
+ error(err.str().c_str());
+ return;
+ }
+
+ try
+ {
+ OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
+
+ m_transform->setInputColorSpaceName(config->getColorSpaceNameByIndex(m_colorSpaceIndex));
+ m_transform->setDisplay(m_displayCstrNames[m_displayIndex]);
+ m_transform->setView(m_viewCstrNames[m_viewIndex]);
+
+ // Specify an (optional) linear color correction
+ {
+ float m44[16];
+ float offset4[4];
+
+ const float slope4f[] = { m_gain, m_gain, m_gain, m_gain };
+ OCIO::MatrixTransform::Scale(m44, offset4, slope4f);
+
+ OCIO::MatrixTransformRcPtr mtx = OCIO::MatrixTransform::Create();
+ mtx->setValue(m44, offset4);
+
+ m_transform->setLinearCC(mtx);
+ }
+
+ // Specify an (optional) post-display transform.
+ {
+ float exponent = 1.0f/std::max(1e-6f, m_gamma);
+ const float exponent4f[] = { exponent, exponent, exponent, exponent };
+ OCIO::ExponentTransformRcPtr cc = OCIO::ExponentTransform::Create();
+ cc->setValue(exponent4f);
+ m_transform->setDisplayCC(cc);
+ }
+
+ // Add Channel swizzling
+ {
+ int channelHot[4] = { 0, 0, 0, 0};
+
+ switch(m_channel)
+ {
+ case 0: // Luma
+ channelHot[0] = 1;
+ channelHot[1] = 1;
+ channelHot[2] = 1;
+ break;
+ case 1: // Channel overlay mode. Do rgb, and then swizzle later
+ channelHot[0] = 1;
+ channelHot[1] = 1;
+ channelHot[2] = 1;
+ channelHot[3] = 1;
+ break;
+ case 2: // RGB
+ channelHot[0] = 1;
+ channelHot[1] = 1;
+ channelHot[2] = 1;
+ channelHot[3] = 1;
+ break;
+ case 3: // R
+ channelHot[0] = 1;
+ break;
+ case 4: // G
+ channelHot[1] = 1;
+ break;
+ case 5: // B
+ channelHot[2] = 1;
+ break;
+ case 6: // A
+ channelHot[3] = 1;
+ break;
+ default:
+ break;
+ }
+
+ float lumacoef[3];
+ config->getDefaultLumaCoefs(lumacoef);
+ float m44[16];
+ float offset[4];
+ OCIO::MatrixTransform::View(m44, offset, channelHot, lumacoef);
+ OCIO::MatrixTransformRcPtr swizzle = OCIO::MatrixTransform::Create();
+ swizzle->setValue(m44, offset);
+ m_transform->setChannelView(swizzle);
+ }
+
+ OCIO::ConstContextRcPtr context = getLocalContext();
+ m_processor = config->getProcessor(context,
+ m_transform,
+ OCIO::TRANSFORM_DIR_FORWARD);
+ }
+ catch(OCIO::Exception &e)
+ {
+ error(e.what());
+ return;
+ }
+
+ if(m_processor->isNoOp())
+ {
+ set_out_channels(DD::Image::Mask_None); // prevents engine() from being called
+ } else {
+ set_out_channels(DD::Image::Mask_All);
+ }
+
+ DD::Image::PixelIop::_validate(for_real);
+}
+
+// Note: Same as OCIO ColorSpace::in_channels.
+void OCIODisplay::in_channels(int /* n unused */, DD::Image::ChannelSet& mask) const
+{
+ DD::Image::ChannelSet done;
+ foreach(c, mask)
+ {
+ if ((m_layersToProcess & c) && DD::Image::colourIndex(c) < 4 && !(done & c))
+ {
+ done.addBrothers(c, 4);
+ }
+ }
+ mask += done;
+}
+
+// Note: Same as OCIO ColorSpace::pixel_engine.
+void OCIODisplay::pixel_engine(
+ const DD::Image::Row& in,
+ int /* rowY unused */, int rowX, int rowXBound,
+ DD::Image::ChannelMask outputChannels,
+ DD::Image::Row& out)
+{
+ int rowWidth = rowXBound - rowX;
+
+ DD::Image::ChannelSet done;
+ foreach (requestedChannel, outputChannels)
+ {
+ // Skip channels which had their trios processed already,
+ if (done & requestedChannel)
+ {
+ continue;
+ }
+
+ // Pass through channels which are not selected for processing
+ // and non-rgb channels.
+ if (!(m_layersToProcess & requestedChannel))
+ {
+ out.copy(in, requestedChannel, rowX, rowXBound);
+ continue;
+ }
+
+ DD::Image::Channel rChannel = DD::Image::brother(requestedChannel, 0);
+ DD::Image::Channel gChannel = DD::Image::brother(requestedChannel, 1);
+ DD::Image::Channel bChannel = DD::Image::brother(requestedChannel, 2);
+ DD::Image::Channel aChannel = outputChannels.next(bChannel);
+
+ done += rChannel;
+ done += gChannel;
+ done += bChannel;
+ done += aChannel;
+
+ const float *rIn = in[rChannel] + rowX;
+ const float *gIn = in[gChannel] + rowX;
+ const float *bIn = in[bChannel] + rowX;
+ const float *aIn = in[aChannel] + rowX;
+
+ float *rOut = out.writable(rChannel) + rowX;
+ float *gOut = out.writable(gChannel) + rowX;
+ float *bOut = out.writable(bChannel) + rowX;
+ float *aOut = out.writable(aChannel) + rowX;
+
+ // OCIO modifies in-place
+ // Note: xOut can equal xIn in some circumstances, such as when the
+ // 'Black' (throwaway) scanline is uses. We thus must guard memcpy,
+ // which does not allow for overlapping regions.
+ if (rOut != rIn) memcpy(rOut, rIn, sizeof(float)*rowWidth);
+ if (gOut != gIn) memcpy(gOut, gIn, sizeof(float)*rowWidth);
+ if (bOut != bIn) memcpy(bOut, bIn, sizeof(float)*rowWidth);
+ if (aOut != aIn) memcpy(aOut, aIn, sizeof(float)*rowWidth);
+
+ try
+ {
+ OCIO::PlanarImageDesc img(rOut, gOut, bOut, aOut, rowWidth, /*height*/ 1);
+ m_processor->apply(img);
+ }
+ catch(OCIO::Exception &e)
+ {
+ error(e.what());
+ }
+
+ // Hack to emulate Channel overlay mode
+ if(m_channel == 1)
+ {
+ for(int i=0; i<rowWidth; ++i)
+ {
+ rOut[i] = rOut[i] + (1.0f - rOut[i]) * (0.5f * aOut[i]);
+ gOut[i] = gOut[i] - gOut[i] * (0.5f * aOut[i]);
+ bOut[i] = bOut[i] - bOut[i] * (0.5f * aOut[i]);
+ }
+ }
+ }
+}
+
+void OCIODisplay::refreshDisplayTransforms()
+{
+ OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
+
+ if (m_displayIndex < 0 || m_displayIndex >= static_cast<int>(m_displayNames.size()))
+ {
+ std::ostringstream err;
+ err << "No or invalid display specified (index " << m_displayIndex << ").";
+ std::cerr << err.str(); // This can't set an error, as it's called in the constructor
+ return;
+ }
+
+ const char * display = m_displayCstrNames[m_displayIndex];
+ int numViews = config->getNumViews(display);
+ std::string defaultViewName = config->getDefaultView(display);
+
+ // Try to maintain an old transform name, or use the default.
+ bool hasOldViewName = false;
+ std::string oldViewName = "";
+ if (m_viewIndex >= 0 && m_viewIndex < static_cast<int>(m_viewNames.size()))
+ {
+ hasOldViewName = true;
+ oldViewName = m_viewNames[m_viewIndex];
+ }
+ int defaultViewIndex = 0, newViewIndex = -1;
+
+ m_viewCstrNames.clear();
+ m_viewNames.clear();
+
+ for(int i = 0; i < numViews; i++)
+ {
+ std::string view = config->getView(display, i);
+ m_viewNames.push_back(view);
+ if (hasOldViewName && view == oldViewName)
+ {
+ newViewIndex = i;
+ }
+ if (view == defaultViewName)
+ {
+ defaultViewIndex = i;
+ }
+ }
+
+ for(unsigned int i=0; i<m_viewNames.size(); ++i)
+ {
+ m_viewCstrNames.push_back(m_viewNames[i].c_str());
+ }
+ m_viewCstrNames.push_back(NULL);
+
+ if (newViewIndex == -1)
+ {
+ newViewIndex = defaultViewIndex;
+ }
+
+ if (m_viewKnob == NULL)
+ {
+ m_viewIndex = newViewIndex;
+ }
+ else
+ {
+ DD::Image::Enumeration_KnobI *enumKnob = m_viewKnob->enumerationKnob();
+ enumKnob->menu(m_viewNames);
+ m_viewKnob->set_value(newViewIndex);
+ }
+}
+
+int OCIODisplay::knob_changed(DD::Image::Knob *k)
+{
+ if (k == m_displayKnob && m_displayKnob != NULL)
+ {
+ refreshDisplayTransforms();
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+const DD::Image::Op::Description OCIODisplay::description("OCIODisplay", build);
+
+const char* OCIODisplay::Class() const
+{
+ return description.name;
+}
+
+const char* OCIODisplay::displayName() const
+{
+ return description.name;
+}
+
+const char* OCIODisplay::node_help() const
+{
+ // TODO more detailed help text
+ return "Use OpenColorIO to convert for a display device.";
+}
+
+
+DD::Image::Op* build(Node *node)
+{
+ DD::Image::NukeWrapper *op = new DD::Image::NukeWrapper(new OCIODisplay(node));
+ op->noMix();
+ op->noMask();
+ op->noChannels(); // prefer our own channels control without checkboxes / alpha
+ op->noUnpremult();
+ return op;
+}