/** * OpenColorIO Display Iop. */ #include "OCIODisplay.h" namespace OCIO = OCIO_NAMESPACE; #include #include #include #include #include #include #include #include #include #include #include // 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; igetNumColorSpaces(); ++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; igetNumDisplays(); ++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; igetCurrentContext(); 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(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= static_cast(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(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; ienumerationKnob(); 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; }