/src/nuke/OCIOLookTransform/OCIOLookTransform.cpp

http://github.com/imageworks/OpenColorIO · C++ · 533 lines · 417 code · 83 blank · 33 comment · 46 complexity · 7a67896f6a5647d09af8bc16525a4c93 MD5 · raw file

  1. /**
  2. * OpenColorIO ColorSpace Iop.
  3. */
  4. #include "OCIOLookTransform.h"
  5. namespace OCIO = OCIO_NAMESPACE;
  6. #include <string>
  7. #include <sstream>
  8. #include <stdexcept>
  9. #include <DDImage/Channel.h>
  10. #include <DDImage/PixelIop.h>
  11. #include <DDImage/NukeWrapper.h>
  12. #include <DDImage/Row.h>
  13. #include <DDImage/Knobs.h>
  14. #include <DDImage/ddImageVersionNumbers.h>
  15. // Should we use cascasing ColorSpace menus
  16. #if defined kDDImageVersionInteger && (kDDImageVersionInteger>=62300)
  17. #define OCIO_CASCADE
  18. #endif
  19. OCIOLookTransform::OCIOLookTransform(Node *n) : DD::Image::PixelIop(n)
  20. {
  21. m_hasColorSpaces = false;
  22. m_inputColorSpaceIndex = 0;
  23. m_outputColorSpaceIndex = 0;
  24. m_dirIndex = 0;
  25. m_ignoreErrors = false;
  26. m_reload_version = 1;
  27. // Query the colorspace names from the current config
  28. // TODO (when to) re-grab the list of available colorspaces? How to save/load?
  29. OCIO::ConstConfigRcPtr config;
  30. std::string linear;
  31. try
  32. {
  33. config = OCIO::GetCurrentConfig();
  34. OCIO::ConstColorSpaceRcPtr linearcs = config->getColorSpace(OCIO::ROLE_SCENE_LINEAR);
  35. if(!linearcs) throw std::runtime_error("ROLE_SCENE_LINEAR not defined.");
  36. linear = linearcs->getName();
  37. if(config->getNumLooks()>0)
  38. {
  39. m_look = config->getLookNameByIndex(0);
  40. }
  41. std::ostringstream os;
  42. os << "Specify the look(s) to apply, as predefined in the OpenColorIO ";
  43. os << "configuration. This may be the name of a single look, or a ";
  44. os << "combination of looks using the 'look syntax' (outlined below)\n\n";
  45. std::string firstlook = "a";
  46. std::string secondlook = "b";
  47. if(config->getNumLooks()>0)
  48. {
  49. os << "Looks: ";
  50. for(int i = 0; i<config->getNumLooks(); ++i)
  51. {
  52. if(i!=0) os << ", ";
  53. const char * lookname = config->getLookNameByIndex(i);
  54. os << lookname;
  55. if(i==0) firstlook = lookname;
  56. if(i==1) secondlook = lookname;
  57. }
  58. os << "\n\n";
  59. }
  60. else
  61. {
  62. os << "NO LOOKS DEFINED -- ";
  63. os << "This node cannot be used until looks are added to the OCIO Configuration. ";
  64. os << "See opencolorio.org for examples.\n\n";
  65. }
  66. os << "Look Syntax:\n";
  67. os << "Multiple looks are combined with commas: '";
  68. os << firstlook << ", " << secondlook << "'\n";
  69. os << "Direction is specified with +/- prefixes: '";
  70. os << "+" << firstlook << ", -" << secondlook << "'\n";
  71. os << "Missing look 'fallbacks' specified with |: '";
  72. os << firstlook << ", -" << secondlook << " | -" << secondlook << "'";
  73. m_lookhelp = os.str();
  74. }
  75. catch (const OCIO::Exception& e)
  76. {
  77. std::cerr << "OCIOLookTransform: " << e.what() << std::endl;
  78. }
  79. catch (...)
  80. {
  81. std::cerr << "OCIOLookTransform: Unknown exception during OCIO setup." << std::endl;
  82. }
  83. if(!config)
  84. {
  85. m_hasColorSpaces = false;
  86. return;
  87. }
  88. for(int i = 0; i < config->getNumColorSpaces(); i++)
  89. {
  90. std::string csname = config->getColorSpaceNameByIndex(i);
  91. #ifdef OCIO_CASCADE
  92. std::string family = config->getColorSpace(csname.c_str())->getFamily();
  93. if(family.empty())
  94. m_colorSpaceNames.push_back(csname.c_str());
  95. else
  96. m_colorSpaceNames.push_back(family + "/" + csname);
  97. #else
  98. m_colorSpaceNames.push_back(csname);
  99. #endif
  100. if(csname == linear)
  101. {
  102. m_inputColorSpaceIndex = i;
  103. m_outputColorSpaceIndex = i;
  104. }
  105. }
  106. // Step 2: Create a cstr array for passing to Nuke
  107. // (This must be done in a second pass, lest the original strings be reallocated)
  108. for(unsigned int i=0; i<m_colorSpaceNames.size(); ++i)
  109. {
  110. m_inputColorSpaceCstrNames.push_back(m_colorSpaceNames[i].c_str());
  111. m_outputColorSpaceCstrNames.push_back(m_colorSpaceNames[i].c_str());
  112. }
  113. m_inputColorSpaceCstrNames.push_back(NULL);
  114. m_outputColorSpaceCstrNames.push_back(NULL);
  115. m_hasColorSpaces = (!m_colorSpaceNames.empty());
  116. if(!m_hasColorSpaces)
  117. {
  118. std::cerr << "OCIOLookTransform: No ColorSpaces available for input and/or output." << std::endl;
  119. }
  120. }
  121. OCIOLookTransform::~OCIOLookTransform()
  122. {
  123. }
  124. namespace
  125. {
  126. static const char * directions[] = { "forward", "inverse", 0 };
  127. }
  128. void OCIOLookTransform::knobs(DD::Image::Knob_Callback f)
  129. {
  130. #ifdef OCIO_CASCADE
  131. DD::Image::CascadingEnumeration_knob(f,
  132. &m_inputColorSpaceIndex, &m_inputColorSpaceCstrNames[0], "in_colorspace", "in");
  133. #else
  134. DD::Image::Enumeration_knob(f,
  135. &m_inputColorSpaceIndex, &m_inputColorSpaceCstrNames[0], "in_colorspace", "in");
  136. #endif
  137. DD::Image::SetFlags(f, DD::Image::Knob::ALWAYS_SAVE);
  138. DD::Image::Tooltip(f, "Input data is taken to be in this colorspace.");
  139. DD::Image::String_knob(f, &m_look, "look");
  140. DD::Image::Tooltip(f, m_lookhelp.c_str());
  141. DD::Image::SetFlags(f, DD::Image::Knob::ALWAYS_SAVE);
  142. DD::Image::Spacer(f, 8);
  143. Enumeration_knob(f, &m_dirIndex, directions, "direction", "direction");
  144. DD::Image::Tooltip(f, "Specify the look transform direction. in/out colorspace handling is not affected.");
  145. DD::Image::ClearFlags(f, DD::Image::Knob::STARTLINE );
  146. // Reload button, and hidden "version" knob to invalidate cache on reload
  147. DD::Image::Spacer(f, 8);
  148. Button(f, "reload", "reload");
  149. DD::Image::Tooltip(f, "Reload all files used in the underlying Look(s).");
  150. Int_knob(f, &m_reload_version, "version");
  151. DD::Image::SetFlags(f, DD::Image::Knob::HIDDEN);
  152. #ifdef OCIO_CASCADE
  153. DD::Image::CascadingEnumeration_knob(f,
  154. &m_outputColorSpaceIndex, &m_outputColorSpaceCstrNames[0], "out_colorspace", "out");
  155. #else
  156. DD::Image::Enumeration_knob(f,
  157. &m_outputColorSpaceIndex, &m_outputColorSpaceCstrNames[0], "out_colorspace", "out");
  158. #endif
  159. DD::Image::SetFlags(f, DD::Image::Knob::ALWAYS_SAVE);
  160. DD::Image::Tooltip(f, "Image data is converted to this colorspace for output.");
  161. DD::Image::Bool_knob(f, &m_ignoreErrors, "ignore_errors", "ignore errors");
  162. DD::Image::Tooltip(f, "If enabled, looks that cannot find the specified correction"
  163. " are treated as a normal ColorSpace conversion instead of triggering a render error.");
  164. DD::Image::SetFlags(f, DD::Image::Knob::STARTLINE );
  165. }
  166. OCIO::ConstContextRcPtr OCIOLookTransform::getLocalContext()
  167. {
  168. OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
  169. OCIO::ConstContextRcPtr context = config->getCurrentContext();
  170. OCIO::ContextRcPtr mutableContext;
  171. if(!m_contextKey1.empty())
  172. {
  173. if(!mutableContext) mutableContext = context->createEditableCopy();
  174. mutableContext->setStringVar(m_contextKey1.c_str(), m_contextValue1.c_str());
  175. }
  176. if(!m_contextKey2.empty())
  177. {
  178. if(!mutableContext) mutableContext = context->createEditableCopy();
  179. mutableContext->setStringVar(m_contextKey2.c_str(), m_contextValue2.c_str());
  180. }
  181. if(!m_contextKey3.empty())
  182. {
  183. if(!mutableContext) mutableContext = context->createEditableCopy();
  184. mutableContext->setStringVar(m_contextKey3.c_str(), m_contextValue3.c_str());
  185. }
  186. if(!m_contextKey4.empty())
  187. {
  188. if(!mutableContext) mutableContext = context->createEditableCopy();
  189. mutableContext->setStringVar(m_contextKey4.c_str(), m_contextValue4.c_str());
  190. }
  191. if(mutableContext) context = mutableContext;
  192. return context;
  193. }
  194. void OCIOLookTransform::append(DD::Image::Hash& nodehash)
  195. {
  196. // Incremented to force reloading after rereading the LUT file
  197. nodehash.append(m_reload_version);
  198. // TODO: Hang onto the context, what if getting it
  199. // (and querying getCacheID) is expensive?
  200. try
  201. {
  202. OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
  203. OCIO::ConstContextRcPtr context = getLocalContext();
  204. std::string configCacheID = config->getCacheID(context);
  205. nodehash.append(configCacheID);
  206. }
  207. catch(const OCIO::Exception &e)
  208. {
  209. error(e.what());
  210. }
  211. catch (...)
  212. {
  213. error("OCIOLookTransform: Unknown exception during hash generation.");
  214. }
  215. }
  216. int OCIOLookTransform::knob_changed(DD::Image::Knob* k)
  217. {
  218. if(k->is("reload"))
  219. {
  220. knob("version")->set_value(m_reload_version+1);
  221. OCIO::ClearAllCaches();
  222. return true; // ensure callback is triggered again
  223. }
  224. // Return zero to avoid callbacks for other knobs
  225. return false;
  226. }
  227. void OCIOLookTransform::_validate(bool for_real)
  228. {
  229. if(!m_hasColorSpaces)
  230. {
  231. error("No colorspaces available for input and/or output.");
  232. return;
  233. }
  234. int inputColorSpaceCount = static_cast<int>(m_inputColorSpaceCstrNames.size()) - 1;
  235. if(m_inputColorSpaceIndex < 0 || m_inputColorSpaceIndex >= inputColorSpaceCount)
  236. {
  237. std::ostringstream err;
  238. err << "Input colorspace index (" << m_inputColorSpaceIndex << ") out of range.";
  239. error(err.str().c_str());
  240. return;
  241. }
  242. int outputColorSpaceCount = static_cast<int>(m_outputColorSpaceCstrNames.size()) - 1;
  243. if(m_outputColorSpaceIndex < 0 || m_outputColorSpaceIndex >= outputColorSpaceCount)
  244. {
  245. std::ostringstream err;
  246. err << "Output colorspace index (" << m_outputColorSpaceIndex << ") out of range.";
  247. error(err.str().c_str());
  248. return;
  249. }
  250. try
  251. {
  252. OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
  253. const char * inputName = config->getColorSpaceNameByIndex(m_inputColorSpaceIndex);
  254. const char * outputName = config->getColorSpaceNameByIndex(m_outputColorSpaceIndex);
  255. OCIO::LookTransformRcPtr transform = OCIO::LookTransform::Create();
  256. transform->setLooks(m_look.c_str());
  257. OCIO::ConstContextRcPtr context = getLocalContext();
  258. OCIO::TransformDirection direction = OCIO::TRANSFORM_DIR_UNKNOWN;
  259. bool invertTransform = (m_dirIndex == 0) ? false : true;
  260. // Forward
  261. if(!invertTransform)
  262. {
  263. transform->setSrc(inputName);
  264. transform->setDst(outputName);
  265. direction = OCIO::TRANSFORM_DIR_FORWARD;
  266. }
  267. else
  268. {
  269. // The TRANSFORM_DIR_INVERSE applies an inverse for the end-to-end transform,
  270. // which would otherwise do dst->inv look -> src.
  271. // This is an unintuitive result for the artist (who would expect in, out to
  272. // remain unchanged), so we account for that here by flipping src/dst
  273. transform->setSrc(outputName);
  274. transform->setDst(inputName);
  275. direction = OCIO::TRANSFORM_DIR_INVERSE;
  276. }
  277. try
  278. {
  279. m_processor = config->getProcessor(context, transform, direction);
  280. }
  281. // We only catch the exceptions for missing files, and try to succeed
  282. // in this case. All other errors represent more serious problems and
  283. // should fail through.
  284. catch(const OCIO::ExceptionMissingFile &e)
  285. {
  286. if(!m_ignoreErrors) throw;
  287. m_processor = config->getProcessor(context, inputName, outputName);
  288. }
  289. }
  290. catch(const OCIO::Exception &e)
  291. {
  292. error(e.what());
  293. return;
  294. }
  295. catch (...)
  296. {
  297. error("OCIOLookTransform: Unknown exception during _validate.");
  298. return;
  299. }
  300. if(m_processor->isNoOp())
  301. {
  302. set_out_channels(DD::Image::Mask_None); // prevents engine() from being called
  303. } else {
  304. set_out_channels(DD::Image::Mask_All);
  305. }
  306. DD::Image::PixelIop::_validate(for_real);
  307. }
  308. // Note that this is copied by others (OCIODisplay)
  309. void OCIOLookTransform::in_channels(int /* n unused */, DD::Image::ChannelSet& mask) const
  310. {
  311. DD::Image::ChannelSet done;
  312. foreach(c, mask)
  313. {
  314. if (DD::Image::colourIndex(c) < 3 && !(done & c))
  315. {
  316. done.addBrothers(c, 3);
  317. }
  318. }
  319. mask += done;
  320. }
  321. // See Saturation::pixel_engine for a well-commented example.
  322. // Note that this is copied by others (OCIODisplay)
  323. void OCIOLookTransform::pixel_engine(
  324. const DD::Image::Row& in,
  325. int /* rowY unused */, int rowX, int rowXBound,
  326. DD::Image::ChannelMask outputChannels,
  327. DD::Image::Row& out)
  328. {
  329. int rowWidth = rowXBound - rowX;
  330. DD::Image::ChannelSet done;
  331. foreach (requestedChannel, outputChannels)
  332. {
  333. // Skip channels which had their trios processed already,
  334. if (done & requestedChannel)
  335. {
  336. continue;
  337. }
  338. // Pass through channels which are not selected for processing
  339. // and non-rgb channels.
  340. if (colourIndex(requestedChannel) >= 3)
  341. {
  342. out.copy(in, requestedChannel, rowX, rowXBound);
  343. continue;
  344. }
  345. DD::Image::Channel rChannel = DD::Image::brother(requestedChannel, 0);
  346. DD::Image::Channel gChannel = DD::Image::brother(requestedChannel, 1);
  347. DD::Image::Channel bChannel = DD::Image::brother(requestedChannel, 2);
  348. done += rChannel;
  349. done += gChannel;
  350. done += bChannel;
  351. const float *rIn = in[rChannel] + rowX;
  352. const float *gIn = in[gChannel] + rowX;
  353. const float *bIn = in[bChannel] + rowX;
  354. float *rOut = out.writable(rChannel) + rowX;
  355. float *gOut = out.writable(gChannel) + rowX;
  356. float *bOut = out.writable(bChannel) + rowX;
  357. // OCIO modifies in-place
  358. // Note: xOut can equal xIn in some circumstances, such as when the
  359. // 'Black' (throwaway) scanline is uses. We thus must guard memcpy,
  360. // which does not allow for overlapping regions.
  361. if (rOut != rIn) memcpy(rOut, rIn, sizeof(float)*rowWidth);
  362. if (gOut != gIn) memcpy(gOut, gIn, sizeof(float)*rowWidth);
  363. if (bOut != bIn) memcpy(bOut, bIn, sizeof(float)*rowWidth);
  364. try
  365. {
  366. OCIO::PlanarImageDesc img(rOut, gOut, bOut, NULL, rowWidth, /*height*/ 1);
  367. m_processor->apply(img);
  368. }
  369. catch(const OCIO::Exception &e)
  370. {
  371. error(e.what());
  372. }
  373. catch (...)
  374. {
  375. error("OCIOLookTransform: Unknown exception during pixel_engine.");
  376. }
  377. }
  378. }
  379. const DD::Image::Op::Description OCIOLookTransform::description("OCIOLookTransform", build);
  380. const char* OCIOLookTransform::Class() const
  381. {
  382. return description.name;
  383. }
  384. const char* OCIOLookTransform::displayName() const
  385. {
  386. return description.name;
  387. }
  388. const char* OCIOLookTransform::node_help() const
  389. {
  390. static const char * help = "OpenColorIO LookTransform\n\n"
  391. "A 'look' is a named color transform, intended to modify the look of an "
  392. "image in a 'creative' manner (as opposed to a colorspace definion which "
  393. "tends to be technically/mathematically defined).\n\n"
  394. "Examples of looks may be a neutral grade, to be applied to film scans "
  395. "prior to VFX work, or a per-shot DI grade decided on by the director, "
  396. "to be applied just before the viewing transform.\n\n"
  397. "OCIOLooks must be predefined in the OpenColorIO configuration before usage, "
  398. "and often reference per-shot/sequence LUTs/CCs.\n\n"
  399. "See the look knob for further syntax details.\n\n"
  400. "See opencolorio.org for look configuration customization examples.";
  401. return help;
  402. }
  403. // This class is necessary in order to call knobsAtTheEnd(). Otherwise, the NukeWrapper knobs
  404. // will be added to the Context tab instead of the primary tab.
  405. class OCIOLookTransformNukeWrapper : public DD::Image::NukeWrapper
  406. {
  407. public:
  408. OCIOLookTransformNukeWrapper(DD::Image::PixelIop* op) : DD::Image::NukeWrapper(op)
  409. {
  410. }
  411. virtual void attach()
  412. {
  413. wrapped_iop()->attach();
  414. }
  415. virtual void detach()
  416. {
  417. wrapped_iop()->detach();
  418. }
  419. virtual void knobs(DD::Image::Knob_Callback f)
  420. {
  421. OCIOLookTransform* lookIop = dynamic_cast<OCIOLookTransform*>(wrapped_iop());
  422. if(!lookIop) return;
  423. DD::Image::NukeWrapper::knobs(f);
  424. DD::Image::Tab_knob(f, "Context");
  425. {
  426. DD::Image::String_knob(f, &lookIop->m_contextKey1, "key1");
  427. DD::Image::Spacer(f, 10);
  428. DD::Image::String_knob(f, &lookIop->m_contextValue1, "value1");
  429. DD::Image::ClearFlags(f, DD::Image::Knob::STARTLINE);
  430. DD::Image::String_knob(f, &lookIop->m_contextKey2, "key2");
  431. DD::Image::Spacer(f, 10);
  432. DD::Image::String_knob(f, &lookIop->m_contextValue2, "value2");
  433. DD::Image::ClearFlags(f, DD::Image::Knob::STARTLINE);
  434. DD::Image::String_knob(f, &lookIop->m_contextKey3, "key3");
  435. DD::Image::Spacer(f, 10);
  436. DD::Image::String_knob(f, &lookIop->m_contextValue3, "value3");
  437. DD::Image::ClearFlags(f, DD::Image::Knob::STARTLINE);
  438. DD::Image::String_knob(f, &lookIop->m_contextKey4, "key4");
  439. DD::Image::Spacer(f, 10);
  440. DD::Image::String_knob(f, &lookIop->m_contextValue4, "value4");
  441. DD::Image::ClearFlags(f, DD::Image::Knob::STARTLINE);
  442. }
  443. }
  444. };
  445. static DD::Image::Op* build(Node *node)
  446. {
  447. DD::Image::NukeWrapper *op = (new OCIOLookTransformNukeWrapper(new OCIOLookTransform(node)));
  448. op->channels(DD::Image::Mask_RGB);
  449. return op;
  450. }