/src/nuke/OCIOCDLTransform/OCIOCDLTransform.cpp

http://github.com/imageworks/OpenColorIO · C++ · 379 lines · 256 code · 74 blank · 49 comment · 32 complexity · d3cc54283eee1754b188e12eed767620 MD5 · raw file

  1. /**
  2. * OpenColorIO conversion Iop.
  3. */
  4. #include "OCIOCDLTransform.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. const char* OCIOCDLTransform::dirs[] = { "forward", "inverse", 0 };
  15. OCIOCDLTransform::OCIOCDLTransform(Node *n) : DD::Image::PixelIop(n)
  16. {
  17. for (int i = 0; i < 3; i++){
  18. m_slope[i] = 1.0;
  19. m_offset[i] = 0.0;
  20. m_power[i] = 1.0;
  21. }
  22. m_saturation = 1.0;
  23. m_readFromFile = false;
  24. m_dirindex = 0;
  25. m_file = NULL;
  26. m_reload_version = 1;
  27. m_slopeKnob = NULL;
  28. m_offsetKnob = NULL;
  29. m_powerKnob = NULL;
  30. m_saturationKnob = NULL;
  31. m_fileKnob = NULL;
  32. m_cccidKnob = NULL;
  33. m_firstLoad = true;
  34. }
  35. OCIOCDLTransform::~OCIOCDLTransform()
  36. {
  37. }
  38. void OCIOCDLTransform::knobs(DD::Image::Knob_Callback f)
  39. {
  40. // ASC CDL grade numbers
  41. m_slopeKnob = DD::Image::Color_knob(f, m_slope, DD::Image::IRange(0, 4.0), "slope");
  42. m_offsetKnob = DD::Image::Color_knob(f, m_offset, DD::Image::IRange(-0.2, 0.2), "offset");
  43. m_powerKnob = DD::Image::Color_knob(f, m_power, DD::Image::IRange(0.0, 4.0), "power");
  44. m_saturationKnob = DD::Image::Float_knob(f, &m_saturation, DD::Image::IRange(0, 4.0), "saturation");
  45. Enumeration_knob(f, &m_dirindex, dirs, "direction", "direction");
  46. DD::Image::Tooltip(f, "Specify the transform direction.");
  47. DD::Image::Divider(f);
  48. DD::Image::Bool_knob(f, &m_readFromFile, "read_from_file", "read from file");
  49. DD::Image::SetFlags(f, DD::Image::Knob::EARLY_STORE);
  50. DD::Image::Tooltip(f, "Load color correction information from the .cc or .ccc file.");
  51. m_fileKnob = File_knob(f, &m_file, "file", "file");
  52. const char * filehelp = "Specify the src ASC CDL file, on disk, to use for this transform. "
  53. "This can be either a .cc or .ccc file. If .ccc is specified, the cccid is required.";
  54. DD::Image::Tooltip(f, filehelp);
  55. // Reload button, and hidden "version" knob to invalidate cache on reload
  56. Button(f, "reload", "reload");
  57. DD::Image::Tooltip(f, "Reloads specified files");
  58. Int_knob(f, &m_reload_version, "version");
  59. DD::Image::SetFlags(f, DD::Image::Knob::HIDDEN);
  60. DD::Image::SetFlags(f, DD::Image::Knob::ENDLINE);
  61. m_cccidKnob = String_knob(f, &m_cccid, "cccid");
  62. const char * ccchelp = "If the source file is an ASC CDL CCC (color correction collection), "
  63. "this specifies the id to lookup. OpenColorIO::Contexts (envvars) are obeyed.";
  64. DD::Image::Tooltip(f, ccchelp);
  65. /* TODO:
  66. These scripts should be updated to make use of native OCIO APIs, rather than convenience functions
  67. exposed in ocionuke. Ideally, ocionuke would be a minimal module (essentially only ui code) that makes
  68. use of OCIO for all heavy lifting.
  69. */
  70. DD::Image::PyScript_knob(f, "import ocionuke.cdl; ocionuke.cdl.select_cccid_for_filetransform()", "select_cccid", "select cccid");
  71. // Import/export buttons
  72. DD::Image::PyScript_knob(f, "import ocionuke.cdl; ocionuke.cdl.export_as_cc()", "export_cc", "export grade as .cc");
  73. DD::Image::Tooltip(f, "Export this grade as a ColorCorrection XML file, which can be loaded with the OCIOFileTransform, or using a FileTransform in an OCIO config");
  74. DD::Image::PyScript_knob(f, "import ocionuke.cdl; ocionuke.cdl.import_cc_from_xml()", "import_cc", "import from .cc");
  75. DD::Image::Tooltip(f, "Import grade from a ColorCorrection XML file");
  76. DD::Image::Divider(f);
  77. /*
  78. TODO: One thing thats sucks is that we dont apparently have a mechansism to call refreshKnobEnabledState
  79. after the knobs have been loaded, but before scripts have a chance to run. I'd love to have a post-knob
  80. finalize callback opportunity to reload the cdl from file, if needed. The current system will only do the
  81. initial file refresh when either the ui is loaded, or a render is triggered.
  82. */
  83. if(!f.makeKnobs() && m_firstLoad)
  84. {
  85. m_firstLoad = false;
  86. refreshKnobEnabledState();
  87. if(m_readFromFile) loadCDLFromFile();
  88. }
  89. }
  90. void OCIOCDLTransform::refreshKnobEnabledState()
  91. {
  92. if(m_readFromFile)
  93. {
  94. m_slopeKnob->disable();
  95. m_offsetKnob->disable();
  96. m_powerKnob->disable();
  97. m_saturationKnob->disable();
  98. // We leave these active to allow knob re-use with the import/export buttons
  99. //m_fileKnob->enable();
  100. //m_cccidKnob->enable();
  101. loadCDLFromFile();
  102. }
  103. else
  104. {
  105. m_slopeKnob->enable();
  106. m_offsetKnob->enable();
  107. m_powerKnob->enable();
  108. m_saturationKnob->enable();
  109. // We leave these active to allow knob re-use with the import/export buttons
  110. //m_fileKnob->disable();
  111. //m_cccidKnob->disable();
  112. }
  113. }
  114. void OCIOCDLTransform::append(DD::Image::Hash& nodehash)
  115. {
  116. // There is a bug where in Nuke <6.3 the String_knob (used for
  117. // cccid) is not included in the node's hash. Include it manually
  118. // so the node correctly redraws. Appears fixed in in 6.3
  119. nodehash.append(m_cccid.c_str());
  120. // Incremented to force reloading after rereading the LUT file
  121. nodehash.append(m_reload_version);
  122. }
  123. int OCIOCDLTransform::knob_changed(DD::Image::Knob* k)
  124. {
  125. // return true if you want to continue to receive changes for this knob
  126. std::string knobname = k->name();
  127. if(knobname == "read_from_file" || knobname == "file" || knobname == "cccid")
  128. {
  129. refreshKnobEnabledState();
  130. if(m_readFromFile)
  131. {
  132. loadCDLFromFile();
  133. }
  134. return true;
  135. }
  136. else if(knobname == "reload")
  137. {
  138. knob("version")->set_value(m_reload_version+1);
  139. OCIO::ClearAllCaches();
  140. m_firstLoad = true;
  141. return true; // ensure callback is triggered again
  142. }
  143. return false;
  144. }
  145. // Check to see if file exists
  146. // read from file
  147. // set knob values
  148. // throw an error if anything goes wrong
  149. void OCIOCDLTransform::loadCDLFromFile()
  150. {
  151. OCIO::CDLTransformRcPtr transform;
  152. try
  153. {
  154. // This is inexpensive to call multiple times, as OCIO caches results
  155. // internally.
  156. transform = OCIO::CDLTransform::CreateFromFile(m_file, m_cccid.c_str());
  157. }
  158. catch(OCIO::Exception &e)
  159. {
  160. error(e.what());
  161. return;
  162. }
  163. float sop[9];
  164. transform->getSOP(sop);
  165. m_slopeKnob->clear_animated(-1);
  166. m_slopeKnob->set_value(sop[0], 0);
  167. m_slopeKnob->set_value(sop[1], 1);
  168. m_slopeKnob->set_value(sop[2], 2);
  169. m_offsetKnob->clear_animated(-1);
  170. m_offsetKnob->set_value(sop[3], 0);
  171. m_offsetKnob->set_value(sop[4], 1);
  172. m_offsetKnob->set_value(sop[5], 2);
  173. m_powerKnob->clear_animated(-1);
  174. m_powerKnob->set_value(sop[6], 0);
  175. m_powerKnob->set_value(sop[7], 1);
  176. m_powerKnob->set_value(sop[8], 2);
  177. m_saturationKnob->clear_animated(-1);
  178. m_saturationKnob->set_value(transform->getSat());
  179. }
  180. void OCIOCDLTransform::_validate(bool for_real)
  181. {
  182. if(m_firstLoad)
  183. {
  184. m_firstLoad = false;
  185. if(m_readFromFile) loadCDLFromFile();
  186. }
  187. // We must explicitly refresh the enable state here as well,
  188. // as there are some changes (such as expressioned knob updates)
  189. // that wont trigger the knob_changed callback. This allows
  190. // us to catch these here.
  191. refreshKnobEnabledState();
  192. try
  193. {
  194. OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
  195. OCIO::CDLTransformRcPtr cc = OCIO::CDLTransform::Create();
  196. cc->setSlope(m_slope);
  197. cc->setOffset(m_offset);
  198. cc->setPower(m_power);
  199. cc->setSat(m_saturation);
  200. if(m_dirindex == 0) cc->setDirection(OCIO::TRANSFORM_DIR_FORWARD);
  201. else cc->setDirection(OCIO::TRANSFORM_DIR_INVERSE);
  202. m_processor = config->getProcessor(cc);
  203. }
  204. catch(OCIO::Exception &e)
  205. {
  206. error(e.what());
  207. return;
  208. }
  209. if(m_processor->isNoOp())
  210. {
  211. set_out_channels(DD::Image::Mask_None); // prevents engine() from being called
  212. } else {
  213. set_out_channels(DD::Image::Mask_All);
  214. }
  215. DD::Image::PixelIop::_validate(for_real);
  216. }
  217. // Note that this is copied by others (OCIODisplay)
  218. void OCIOCDLTransform::in_channels(int /* n unused */, DD::Image::ChannelSet& mask) const
  219. {
  220. DD::Image::ChannelSet done;
  221. foreach(c, mask)
  222. {
  223. if (DD::Image::colourIndex(c) < 3 && !(done & c))
  224. {
  225. done.addBrothers(c, 3);
  226. }
  227. }
  228. mask += done;
  229. }
  230. // See Saturation::pixel_engine for a well-commented example.
  231. // Note that this is copied by others (OCIODisplay)
  232. void OCIOCDLTransform::pixel_engine(
  233. const DD::Image::Row& in,
  234. int /* rowY unused */, int rowX, int rowXBound,
  235. DD::Image::ChannelMask outputChannels,
  236. DD::Image::Row& out)
  237. {
  238. int rowWidth = rowXBound - rowX;
  239. DD::Image::ChannelSet done;
  240. foreach (requestedChannel, outputChannels)
  241. {
  242. // Skip channels which had their trios processed already,
  243. if (done & requestedChannel)
  244. {
  245. continue;
  246. }
  247. // Pass through channels which are not selected for processing
  248. // and non-rgb channels.
  249. if (colourIndex(requestedChannel) >= 3)
  250. {
  251. out.copy(in, requestedChannel, rowX, rowXBound);
  252. continue;
  253. }
  254. DD::Image::Channel rChannel = DD::Image::brother(requestedChannel, 0);
  255. DD::Image::Channel gChannel = DD::Image::brother(requestedChannel, 1);
  256. DD::Image::Channel bChannel = DD::Image::brother(requestedChannel, 2);
  257. done += rChannel;
  258. done += gChannel;
  259. done += bChannel;
  260. const float *rIn = in[rChannel] + rowX;
  261. const float *gIn = in[gChannel] + rowX;
  262. const float *bIn = in[bChannel] + rowX;
  263. float *rOut = out.writable(rChannel) + rowX;
  264. float *gOut = out.writable(gChannel) + rowX;
  265. float *bOut = out.writable(bChannel) + rowX;
  266. // OCIO modifies in-place
  267. // Note: xOut can equal xIn in some circumstances, such as when the
  268. // 'Black' (throwaway) scanline is uses. We thus must guard memcpy,
  269. // which does not allow for overlapping regions.
  270. if (rOut != rIn) memcpy(rOut, rIn, sizeof(float)*rowWidth);
  271. if (gOut != gIn) memcpy(gOut, gIn, sizeof(float)*rowWidth);
  272. if (bOut != bIn) memcpy(bOut, bIn, sizeof(float)*rowWidth);
  273. try
  274. {
  275. OCIO::PlanarImageDesc img(rOut, gOut, bOut, NULL, rowWidth, /*height*/ 1);
  276. m_processor->apply(img);
  277. }
  278. catch(OCIO::Exception &e)
  279. {
  280. error(e.what());
  281. }
  282. }
  283. }
  284. const DD::Image::Op::Description OCIOCDLTransform::description("OCIOCDLTransform", build);
  285. const char* OCIOCDLTransform::Class() const
  286. {
  287. return description.name;
  288. }
  289. const char* OCIOCDLTransform::displayName() const
  290. {
  291. return description.name;
  292. }
  293. const char* OCIOCDLTransform::node_help() const
  294. {
  295. // TODO more detailed help text
  296. return "Use OpenColorIO to apply an ASC CDL grade. Applied using:\n\n"\
  297. "out = (i * s + o)^p\n\nWhere i is the input value, s is slope, "\
  298. "o is offset and p is power";
  299. }
  300. DD::Image::Op* build(Node *node)
  301. {
  302. DD::Image::NukeWrapper *op = new DD::Image::NukeWrapper(new OCIOCDLTransform(node));
  303. op->channels(DD::Image::Mask_RGB);
  304. return op;
  305. }