/src/nuke/OCIODisplay/OCIODisplay.cpp

http://github.com/imageworks/OpenColorIO · C++ · 561 lines · 455 code · 82 blank · 24 comment · 64 complexity · 6b4363256ed468ab6c0ab9da4ffc6128 MD5 · raw file

  1. /**
  2. * OpenColorIO Display Iop.
  3. */
  4. #include "OCIODisplay.h"
  5. namespace OCIO = OCIO_NAMESPACE;
  6. #include <algorithm>
  7. #include <string>
  8. #include <sstream>
  9. #include <stdexcept>
  10. #include <DDImage/Channel.h>
  11. #include <DDImage/PixelIop.h>
  12. #include <DDImage/NukeWrapper.h>
  13. #include <DDImage/Row.h>
  14. #include <DDImage/Knobs.h>
  15. #include <DDImage/Enumeration_KnobI.h>
  16. #include <DDImage/ddImageVersionNumbers.h>
  17. // Should we use cascasing ColorSpace menus
  18. #if defined kDDImageVersionInteger && (kDDImageVersionInteger>=62300)
  19. #define OCIO_CASCADE
  20. #endif
  21. OCIODisplay::OCIODisplay(Node *n) : DD::Image::PixelIop(n)
  22. {
  23. m_layersToProcess = DD::Image::Mask_RGBA;
  24. m_hasLists = false;
  25. m_colorSpaceIndex = m_displayIndex = m_viewIndex = 0;
  26. m_displayKnob = m_viewKnob = NULL;
  27. m_gain = 1.0;
  28. m_gamma = 1.0;
  29. m_channel = 2;
  30. m_transform = OCIO::DisplayTransform::Create();
  31. try
  32. {
  33. OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
  34. OCIO::ConstColorSpaceRcPtr defaultcs = config->getColorSpace(OCIO::ROLE_SCENE_LINEAR);
  35. if(!defaultcs) throw std::runtime_error("ROLE_SCENE_LINEAR not defined.");
  36. std::string defaultColorSpaceName = defaultcs->getName();
  37. for(int i=0; i<config->getNumColorSpaces(); ++i)
  38. {
  39. std::string csname = config->getColorSpaceNameByIndex(i);
  40. #ifdef OCIO_CASCADE
  41. std::string family = config->getColorSpace(csname.c_str())->getFamily();
  42. if(family.empty())
  43. m_colorSpaceNames.push_back(csname.c_str());
  44. else
  45. m_colorSpaceNames.push_back(family + "/" + csname);
  46. #else
  47. m_colorSpaceNames.push_back(csname);
  48. #endif
  49. if(defaultColorSpaceName == csname)
  50. {
  51. m_colorSpaceIndex = i;
  52. }
  53. }
  54. std::string defaultDisplay = config->getDefaultDisplay();
  55. for(int i=0; i<config->getNumDisplays(); ++i)
  56. {
  57. std::string display = config->getDisplay(i);
  58. m_displayNames.push_back(display);
  59. if(display == defaultDisplay)
  60. {
  61. m_displayIndex = i;
  62. }
  63. }
  64. }
  65. catch(OCIO::Exception& e)
  66. {
  67. std::cerr << "OCIODisplay: " << e.what() << std::endl;
  68. }
  69. catch(...)
  70. {
  71. std::cerr << "OCIODisplay: Unknown exception during OCIO setup." << std::endl;
  72. }
  73. // Build the cstr vectors on our second pass
  74. for(unsigned int i=0; i<m_colorSpaceNames.size(); ++i)
  75. {
  76. m_colorSpaceCstrNames.push_back(m_colorSpaceNames[i].c_str());
  77. }
  78. m_colorSpaceCstrNames.push_back(NULL);
  79. for(unsigned int i=0; i<m_displayNames.size(); ++i)
  80. {
  81. m_displayCstrNames.push_back(m_displayNames[i].c_str());
  82. }
  83. m_displayCstrNames.push_back(NULL);
  84. refreshDisplayTransforms();
  85. m_hasLists = !(m_colorSpaceNames.empty() || m_displayNames.empty() || m_viewNames.empty());
  86. if(!m_hasLists)
  87. {
  88. std::cerr << "OCIODisplay: Missing one or more of colorspaces, display devices, or display transforms." << std::endl;
  89. }
  90. }
  91. OCIODisplay::~OCIODisplay()
  92. {
  93. }
  94. void OCIODisplay::knobs(DD::Image::Knob_Callback f)
  95. {
  96. #ifdef OCIO_CASCADE
  97. DD::Image::CascadingEnumeration_knob(f,
  98. &m_colorSpaceIndex, &m_colorSpaceCstrNames[0], "colorspace", "input colorspace");
  99. #else
  100. DD::Image::Enumeration_knob(f,
  101. &m_colorSpaceIndex, &m_colorSpaceCstrNames[0], "colorspace", "input colorspace");
  102. #endif
  103. DD::Image::SetFlags(f, DD::Image::Knob::ALWAYS_SAVE);
  104. DD::Image::Tooltip(f, "Input data is taken to be in this colorspace.");
  105. m_displayKnob = DD::Image::Enumeration_knob(f,
  106. &m_displayIndex, &m_displayCstrNames[0], "display", "display device");
  107. DD::Image::SetFlags(f, DD::Image::Knob::ALWAYS_SAVE);
  108. DD::Image::Tooltip(f, "Display device for output.");
  109. m_viewKnob = DD::Image::Enumeration_knob(f,
  110. &m_viewIndex, &m_viewCstrNames[0], "view", "view transform");
  111. DD::Image::SetFlags(f, DD::Image::Knob::ALWAYS_SAVE);
  112. DD::Image::Tooltip(f, "Display transform for output.");
  113. DD::Image::Float_knob(f, &m_gain, DD::Image::IRange(1.0 / 64.0f, 64.0f), "gain");
  114. DD::Image::SetFlags(f, DD::Image::Knob::NO_ANIMATION | DD::Image::Knob::NO_UNDO | DD::Image::Knob::LOG_SLIDER);
  115. DD::Image::Tooltip(f, "Exposure adjustment, in scene-linear, prior to the display transform.");
  116. DD::Image::Float_knob(f, &m_gamma, DD::Image::IRange(0, 4), "gamma");
  117. DD::Image::SetFlags(f, DD::Image::Knob::NO_ANIMATION | DD::Image::Knob::NO_UNDO | DD::Image::Knob::LOG_SLIDER);
  118. DD::Image::Tooltip(f, "Gamma correction applied after the display transform.");
  119. static const char* const channelvalues[] = {
  120. "Luminance",
  121. "Matte overlay",
  122. "RGB",
  123. "R",
  124. "G",
  125. "B",
  126. "A",
  127. 0
  128. };
  129. DD::Image::Enumeration_knob(f, &m_channel, channelvalues, "channel_selector", "channel view");
  130. DD::Image::SetFlags(f, DD::Image::Knob::NO_ANIMATION | DD::Image::Knob::NO_UNDO);
  131. DD::Image::Tooltip(f, "Specify which channels to view (prior to the display transform).");
  132. DD::Image::Divider(f);
  133. DD::Image::Input_ChannelSet_knob(f, &m_layersToProcess, 0, "layer", "layer");
  134. // DD::Image::SetFlags(f, DD::Image::Knob::NO_CHECKMARKS | DD::Image::Knob::NO_ALPHA_PULLDOWN);
  135. DD::Image::Tooltip(f, "Set which layer to process. This should be a layer with rgb data.");
  136. DD::Image::Tab_knob(f, "Context");
  137. {
  138. DD::Image::String_knob(f, &m_contextKey1, "key1");
  139. DD::Image::Spacer(f, 10);
  140. DD::Image::String_knob(f, &m_contextValue1, "value1");
  141. DD::Image::ClearFlags(f, DD::Image::Knob::STARTLINE);
  142. DD::Image::String_knob(f, &m_contextKey2, "key2");
  143. DD::Image::Spacer(f, 10);
  144. DD::Image::String_knob(f, &m_contextValue2, "value2");
  145. DD::Image::ClearFlags(f, DD::Image::Knob::STARTLINE);
  146. DD::Image::String_knob(f, &m_contextKey3, "key3");
  147. DD::Image::Spacer(f, 10);
  148. DD::Image::String_knob(f, &m_contextValue3, "value3");
  149. DD::Image::ClearFlags(f, DD::Image::Knob::STARTLINE);
  150. DD::Image::String_knob(f, &m_contextKey4, "key4");
  151. DD::Image::Spacer(f, 10);
  152. DD::Image::String_knob(f, &m_contextValue4, "value4");
  153. DD::Image::ClearFlags(f, DD::Image::Knob::STARTLINE);
  154. }
  155. }
  156. OCIO::ConstContextRcPtr OCIODisplay::getLocalContext()
  157. {
  158. OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
  159. OCIO::ConstContextRcPtr context = config->getCurrentContext();
  160. OCIO::ContextRcPtr mutableContext;
  161. if(!m_contextKey1.empty())
  162. {
  163. if(!mutableContext) mutableContext = context->createEditableCopy();
  164. mutableContext->setStringVar(m_contextKey1.c_str(), m_contextValue1.c_str());
  165. }
  166. if(!m_contextKey2.empty())
  167. {
  168. if(!mutableContext) mutableContext = context->createEditableCopy();
  169. mutableContext->setStringVar(m_contextKey2.c_str(), m_contextValue2.c_str());
  170. }
  171. if(!m_contextKey3.empty())
  172. {
  173. if(!mutableContext) mutableContext = context->createEditableCopy();
  174. mutableContext->setStringVar(m_contextKey3.c_str(), m_contextValue3.c_str());
  175. }
  176. if(!m_contextKey4.empty())
  177. {
  178. if(!mutableContext) mutableContext = context->createEditableCopy();
  179. mutableContext->setStringVar(m_contextKey4.c_str(), m_contextValue4.c_str());
  180. }
  181. if(mutableContext) context = mutableContext;
  182. return context;
  183. }
  184. void OCIODisplay::append(DD::Image::Hash& localhash)
  185. {
  186. // TODO: Hang onto the context, what if getting it
  187. // (and querying getCacheID) is expensive?
  188. try
  189. {
  190. OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
  191. OCIO::ConstContextRcPtr context = getLocalContext();
  192. std::string configCacheID = config->getCacheID(context);
  193. localhash.append(configCacheID);
  194. // This is required due to our custom channel overlay mode post-processing
  195. localhash.append(m_channel);
  196. }
  197. catch(OCIO::Exception &e)
  198. {
  199. error(e.what());
  200. return;
  201. }
  202. }
  203. void OCIODisplay::_validate(bool for_real)
  204. {
  205. input0().validate(for_real);
  206. if(!m_hasLists)
  207. {
  208. error("Missing one or more of colorspaces, display devices, or display transforms.");
  209. return;
  210. }
  211. int nColorSpaces = static_cast<int>(m_colorSpaceNames.size());
  212. if(m_colorSpaceIndex < 0 || m_colorSpaceIndex >= nColorSpaces)
  213. {
  214. std::ostringstream err;
  215. err << "ColorSpace index (" << m_colorSpaceIndex << ") out of range.";
  216. error(err.str().c_str());
  217. return;
  218. }
  219. try
  220. {
  221. OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
  222. m_transform->setInputColorSpaceName(config->getColorSpaceNameByIndex(m_colorSpaceIndex));
  223. m_transform->setDisplay(m_displayCstrNames[m_displayIndex]);
  224. m_transform->setView(m_viewCstrNames[m_viewIndex]);
  225. // Specify an (optional) linear color correction
  226. {
  227. float m44[16];
  228. float offset4[4];
  229. const float slope4f[] = { m_gain, m_gain, m_gain, m_gain };
  230. OCIO::MatrixTransform::Scale(m44, offset4, slope4f);
  231. OCIO::MatrixTransformRcPtr mtx = OCIO::MatrixTransform::Create();
  232. mtx->setValue(m44, offset4);
  233. m_transform->setLinearCC(mtx);
  234. }
  235. // Specify an (optional) post-display transform.
  236. {
  237. float exponent = 1.0f/std::max(1e-6f, m_gamma);
  238. const float exponent4f[] = { exponent, exponent, exponent, exponent };
  239. OCIO::ExponentTransformRcPtr cc = OCIO::ExponentTransform::Create();
  240. cc->setValue(exponent4f);
  241. m_transform->setDisplayCC(cc);
  242. }
  243. // Add Channel swizzling
  244. {
  245. int channelHot[4] = { 0, 0, 0, 0};
  246. switch(m_channel)
  247. {
  248. case 0: // Luma
  249. channelHot[0] = 1;
  250. channelHot[1] = 1;
  251. channelHot[2] = 1;
  252. break;
  253. case 1: // Channel overlay mode. Do rgb, and then swizzle later
  254. channelHot[0] = 1;
  255. channelHot[1] = 1;
  256. channelHot[2] = 1;
  257. channelHot[3] = 1;
  258. break;
  259. case 2: // RGB
  260. channelHot[0] = 1;
  261. channelHot[1] = 1;
  262. channelHot[2] = 1;
  263. channelHot[3] = 1;
  264. break;
  265. case 3: // R
  266. channelHot[0] = 1;
  267. break;
  268. case 4: // G
  269. channelHot[1] = 1;
  270. break;
  271. case 5: // B
  272. channelHot[2] = 1;
  273. break;
  274. case 6: // A
  275. channelHot[3] = 1;
  276. break;
  277. default:
  278. break;
  279. }
  280. float lumacoef[3];
  281. config->getDefaultLumaCoefs(lumacoef);
  282. float m44[16];
  283. float offset[4];
  284. OCIO::MatrixTransform::View(m44, offset, channelHot, lumacoef);
  285. OCIO::MatrixTransformRcPtr swizzle = OCIO::MatrixTransform::Create();
  286. swizzle->setValue(m44, offset);
  287. m_transform->setChannelView(swizzle);
  288. }
  289. OCIO::ConstContextRcPtr context = getLocalContext();
  290. m_processor = config->getProcessor(context,
  291. m_transform,
  292. OCIO::TRANSFORM_DIR_FORWARD);
  293. }
  294. catch(OCIO::Exception &e)
  295. {
  296. error(e.what());
  297. return;
  298. }
  299. if(m_processor->isNoOp())
  300. {
  301. set_out_channels(DD::Image::Mask_None); // prevents engine() from being called
  302. } else {
  303. set_out_channels(DD::Image::Mask_All);
  304. }
  305. DD::Image::PixelIop::_validate(for_real);
  306. }
  307. // Note: Same as OCIO ColorSpace::in_channels.
  308. void OCIODisplay::in_channels(int /* n unused */, DD::Image::ChannelSet& mask) const
  309. {
  310. DD::Image::ChannelSet done;
  311. foreach(c, mask)
  312. {
  313. if ((m_layersToProcess & c) && DD::Image::colourIndex(c) < 4 && !(done & c))
  314. {
  315. done.addBrothers(c, 4);
  316. }
  317. }
  318. mask += done;
  319. }
  320. // Note: Same as OCIO ColorSpace::pixel_engine.
  321. void OCIODisplay::pixel_engine(
  322. const DD::Image::Row& in,
  323. int /* rowY unused */, int rowX, int rowXBound,
  324. DD::Image::ChannelMask outputChannels,
  325. DD::Image::Row& out)
  326. {
  327. int rowWidth = rowXBound - rowX;
  328. DD::Image::ChannelSet done;
  329. foreach (requestedChannel, outputChannels)
  330. {
  331. // Skip channels which had their trios processed already,
  332. if (done & requestedChannel)
  333. {
  334. continue;
  335. }
  336. // Pass through channels which are not selected for processing
  337. // and non-rgb channels.
  338. if (!(m_layersToProcess & requestedChannel))
  339. {
  340. out.copy(in, requestedChannel, rowX, rowXBound);
  341. continue;
  342. }
  343. DD::Image::Channel rChannel = DD::Image::brother(requestedChannel, 0);
  344. DD::Image::Channel gChannel = DD::Image::brother(requestedChannel, 1);
  345. DD::Image::Channel bChannel = DD::Image::brother(requestedChannel, 2);
  346. DD::Image::Channel aChannel = outputChannels.next(bChannel);
  347. done += rChannel;
  348. done += gChannel;
  349. done += bChannel;
  350. done += aChannel;
  351. const float *rIn = in[rChannel] + rowX;
  352. const float *gIn = in[gChannel] + rowX;
  353. const float *bIn = in[bChannel] + rowX;
  354. const float *aIn = in[aChannel] + rowX;
  355. float *rOut = out.writable(rChannel) + rowX;
  356. float *gOut = out.writable(gChannel) + rowX;
  357. float *bOut = out.writable(bChannel) + rowX;
  358. float *aOut = out.writable(aChannel) + rowX;
  359. // OCIO modifies in-place
  360. // Note: xOut can equal xIn in some circumstances, such as when the
  361. // 'Black' (throwaway) scanline is uses. We thus must guard memcpy,
  362. // which does not allow for overlapping regions.
  363. if (rOut != rIn) memcpy(rOut, rIn, sizeof(float)*rowWidth);
  364. if (gOut != gIn) memcpy(gOut, gIn, sizeof(float)*rowWidth);
  365. if (bOut != bIn) memcpy(bOut, bIn, sizeof(float)*rowWidth);
  366. if (aOut != aIn) memcpy(aOut, aIn, sizeof(float)*rowWidth);
  367. try
  368. {
  369. OCIO::PlanarImageDesc img(rOut, gOut, bOut, aOut, rowWidth, /*height*/ 1);
  370. m_processor->apply(img);
  371. }
  372. catch(OCIO::Exception &e)
  373. {
  374. error(e.what());
  375. }
  376. // Hack to emulate Channel overlay mode
  377. if(m_channel == 1)
  378. {
  379. for(int i=0; i<rowWidth; ++i)
  380. {
  381. rOut[i] = rOut[i] + (1.0f - rOut[i]) * (0.5f * aOut[i]);
  382. gOut[i] = gOut[i] - gOut[i] * (0.5f * aOut[i]);
  383. bOut[i] = bOut[i] - bOut[i] * (0.5f * aOut[i]);
  384. }
  385. }
  386. }
  387. }
  388. void OCIODisplay::refreshDisplayTransforms()
  389. {
  390. OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
  391. if (m_displayIndex < 0 || m_displayIndex >= static_cast<int>(m_displayNames.size()))
  392. {
  393. std::ostringstream err;
  394. err << "No or invalid display specified (index " << m_displayIndex << ").";
  395. std::cerr << err.str(); // This can't set an error, as it's called in the constructor
  396. return;
  397. }
  398. const char * display = m_displayCstrNames[m_displayIndex];
  399. int numViews = config->getNumViews(display);
  400. std::string defaultViewName = config->getDefaultView(display);
  401. // Try to maintain an old transform name, or use the default.
  402. bool hasOldViewName = false;
  403. std::string oldViewName = "";
  404. if (m_viewIndex >= 0 && m_viewIndex < static_cast<int>(m_viewNames.size()))
  405. {
  406. hasOldViewName = true;
  407. oldViewName = m_viewNames[m_viewIndex];
  408. }
  409. int defaultViewIndex = 0, newViewIndex = -1;
  410. m_viewCstrNames.clear();
  411. m_viewNames.clear();
  412. for(int i = 0; i < numViews; i++)
  413. {
  414. std::string view = config->getView(display, i);
  415. m_viewNames.push_back(view);
  416. if (hasOldViewName && view == oldViewName)
  417. {
  418. newViewIndex = i;
  419. }
  420. if (view == defaultViewName)
  421. {
  422. defaultViewIndex = i;
  423. }
  424. }
  425. for(unsigned int i=0; i<m_viewNames.size(); ++i)
  426. {
  427. m_viewCstrNames.push_back(m_viewNames[i].c_str());
  428. }
  429. m_viewCstrNames.push_back(NULL);
  430. if (newViewIndex == -1)
  431. {
  432. newViewIndex = defaultViewIndex;
  433. }
  434. if (m_viewKnob == NULL)
  435. {
  436. m_viewIndex = newViewIndex;
  437. }
  438. else
  439. {
  440. DD::Image::Enumeration_KnobI *enumKnob = m_viewKnob->enumerationKnob();
  441. enumKnob->menu(m_viewNames);
  442. m_viewKnob->set_value(newViewIndex);
  443. }
  444. }
  445. int OCIODisplay::knob_changed(DD::Image::Knob *k)
  446. {
  447. if (k == m_displayKnob && m_displayKnob != NULL)
  448. {
  449. refreshDisplayTransforms();
  450. return 1;
  451. }
  452. else
  453. {
  454. return 0;
  455. }
  456. }
  457. const DD::Image::Op::Description OCIODisplay::description("OCIODisplay", build);
  458. const char* OCIODisplay::Class() const
  459. {
  460. return description.name;
  461. }
  462. const char* OCIODisplay::displayName() const
  463. {
  464. return description.name;
  465. }
  466. const char* OCIODisplay::node_help() const
  467. {
  468. // TODO more detailed help text
  469. return "Use OpenColorIO to convert for a display device.";
  470. }
  471. DD::Image::Op* build(Node *node)
  472. {
  473. DD::Image::NukeWrapper *op = new DD::Image::NukeWrapper(new OCIODisplay(node));
  474. op->noMix();
  475. op->noMask();
  476. op->noChannels(); // prefer our own channels control without checkboxes / alpha
  477. op->noUnpremult();
  478. return op;
  479. }