1 /**
2  * OpenColorIO FileTransform Iop.
3  */
4 
5 #include "OCIOFileTransform.h"
6 
7 namespace OCIO = OCIO_NAMESPACE;
8 
9 #include <string>
10 #include <sstream>
11 #include <stdexcept>
12 
13 #include <DDImage/Channel.h>
14 #include <DDImage/PixelIop.h>
15 #include <DDImage/NukeWrapper.h>
16 #include <DDImage/Row.h>
17 #include <DDImage/Knobs.h>
18 
OCIOFileTransform(Node * n)19 OCIOFileTransform::OCIOFileTransform(Node *n) : DD::Image::PixelIop(n)
20 {
21     m_file = NULL;
22     m_dirindex = 0;
23     m_interpindex = 1;
24     m_reload_version = 1;
25 }
26 
~OCIOFileTransform()27 OCIOFileTransform::~OCIOFileTransform()
28 {
29 
30 }
31 
32 const char* OCIOFileTransform::dirs[] = { "forward", "inverse", 0 };
33 
34 const char* OCIOFileTransform::interp[] = { "nearest", "linear", "tetrahedral", "best", 0 };
35 
knobs(DD::Image::Knob_Callback f)36 void OCIOFileTransform::knobs(DD::Image::Knob_Callback f)
37 {
38     File_knob(f, &m_file, "file", "file");
39     DD::Image::Tooltip(f, "Specify the file, on disk, to use for this transform. See the node help for the list of supported formats.");
40 
41     // Reload button, and hidden "version" knob to invalidate cache on reload
42     Button(f, "reload", "reload");
43     DD::Image::Tooltip(f, "Reloads specified files");
44     Int_knob(f, &m_reload_version, "version");
45     DD::Image::SetFlags(f, DD::Image::Knob::HIDDEN);
46 
47     String_knob(f, &m_cccid, "cccid");
48     const char * srchelp2 = "If the source file is an ASC CDL CCC (color correction collection), "
49     "this specifys the id to lookup. OpenColorIO::Contexts (envvars) are obeyed.";
50     DD::Image::Tooltip(f, srchelp2);
51 
52     DD::Image::PyScript_knob(f, "import ocionuke.cdl; ocionuke.cdl.select_cccid_for_filetransform(fileknob='file', cccidknob = 'cccid')", "select_cccid", "select cccid");
53 
54     Enumeration_knob(f, &m_dirindex, dirs, "direction", "direction");
55     DD::Image::Tooltip(f, "Specify the transform direction.");
56 
57     Enumeration_knob(f, &m_interpindex, interp, "interpolation", "interpolation");
58     DD::Image::Tooltip(f, "Specify the interpolation method. For files that are not LUTs (mtx, etc) this is ignored.");
59 }
60 
_validate(bool for_real)61 void OCIOFileTransform::_validate(bool for_real)
62 {
63     if(!m_file)
64     {
65         error("The source file must be specified.");
66         return;
67     }
68 
69     try
70     {
71         OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
72 
73         OCIO::FileTransformRcPtr transform = OCIO::FileTransform::Create();
74         transform->setSrc(m_file);
75 
76         transform->setCCCId(m_cccid.c_str());
77 
78         if(m_dirindex == 0) transform->setDirection(OCIO::TRANSFORM_DIR_FORWARD);
79         else transform->setDirection(OCIO::TRANSFORM_DIR_INVERSE);
80 
81         if(m_interpindex == 0) transform->setInterpolation(OCIO::INTERP_NEAREST);
82         else if(m_interpindex == 1) transform->setInterpolation(OCIO::INTERP_LINEAR);
83         else if(m_interpindex == 2) transform->setInterpolation(OCIO::INTERP_TETRAHEDRAL);
84         else if(m_interpindex == 3) transform->setInterpolation(OCIO::INTERP_BEST);
85         else
86         {
87             // Should never happen
88             error("Interpolation value out of bounds");
89             return;
90         }
91 
92         m_processor = config->getProcessor(transform, OCIO::TRANSFORM_DIR_FORWARD);
93     }
94     catch(OCIO::Exception &e)
95     {
96         error(e.what());
97         return;
98     }
99 
100     if(m_processor->isNoOp())
101     {
102         set_out_channels(DD::Image::Mask_None); // prevents engine() from being called
103     } else {
104         set_out_channels(DD::Image::Mask_All);
105     }
106 
107     DD::Image::PixelIop::_validate(for_real);
108 }
109 
110 // Note that this is copied by others (OCIODisplay)
in_channels(int,DD::Image::ChannelSet & mask) const111 void OCIOFileTransform::in_channels(int /* n unused */, DD::Image::ChannelSet& mask) const
112 {
113     DD::Image::ChannelSet done;
114     foreach(c, mask)
115     {
116         if (DD::Image::colourIndex(c) < 3 && !(done & c))
117         {
118             done.addBrothers(c, 3);
119         }
120     }
121     mask += done;
122 }
123 
append(DD::Image::Hash & nodehash)124 void OCIOFileTransform::append(DD::Image::Hash& nodehash)
125 {
126     // There is a bug where in Nuke <6.3 the String_knob (used for
127     // cccid) is not included in the node's hash. Include it manually
128     // so the node correctly redraws. Appears fixed in in 6.3
129     nodehash.append(m_cccid.c_str());
130 
131     // Incremented to force reloading after rereading the LUT file
132     nodehash.append(m_reload_version);
133 }
134 
knob_changed(DD::Image::Knob * k)135 int OCIOFileTransform::knob_changed(DD::Image::Knob* k)
136 {
137     // Only show the cccid knob when loading a .cc/.ccc file. Set
138     // hidden state when the src is changed, or the node properties
139     // are shown
140     if(k->is("file") | k->is("showPanel"))
141     {
142         // Convoluted equiv to pysting::endswith(m_file, ".ccc")
143         // TODO: Could this be queried from the processor?
144         const std::string srcstring = m_file;
145         const std::string cccext = "ccc";
146         const std::string ccext = "cc";
147         if(std::equal(cccext.rbegin(), cccext.rend(), srcstring.rbegin()) ||
148            std::equal(ccext.rbegin(), ccext.rend(), srcstring.rbegin()))
149         {
150             knob("cccid")->show();
151             knob("select_cccid")->show();
152         }
153         else
154         {
155             knob("cccid")->hide();
156             knob("select_cccid")->hide();
157         }
158 
159         // Ensure this callback is always triggered (for src knob)
160         return true;
161     }
162 
163     if(k->is("reload"))
164     {
165         knob("version")->set_value(m_reload_version+1);
166         OCIO::ClearAllCaches();
167 
168         return true; // ensure callback is triggered again
169     }
170 
171     // Return zero to avoid callbacks for other knobs
172     return false;
173 }
174 
175 // See Saturation::pixel_engine for a well-commented example.
176 // Note that this is copied by others (OCIODisplay)
pixel_engine(const DD::Image::Row & in,int,int rowX,int rowXBound,DD::Image::ChannelMask outputChannels,DD::Image::Row & out)177 void OCIOFileTransform::pixel_engine(
178     const DD::Image::Row& in,
179     int /* rowY unused */, int rowX, int rowXBound,
180     DD::Image::ChannelMask outputChannels,
181     DD::Image::Row& out)
182 {
183     int rowWidth = rowXBound - rowX;
184 
185     DD::Image::ChannelSet done;
186     foreach (requestedChannel, outputChannels)
187     {
188         // Skip channels which had their trios processed already,
189         if (done & requestedChannel)
190         {
191             continue;
192         }
193 
194         // Pass through channels which are not selected for processing
195         // and non-rgb channels.
196         if (colourIndex(requestedChannel) >= 3)
197         {
198             out.copy(in, requestedChannel, rowX, rowXBound);
199             continue;
200         }
201 
202         DD::Image::Channel rChannel = DD::Image::brother(requestedChannel, 0);
203         DD::Image::Channel gChannel = DD::Image::brother(requestedChannel, 1);
204         DD::Image::Channel bChannel = DD::Image::brother(requestedChannel, 2);
205 
206         done += rChannel;
207         done += gChannel;
208         done += bChannel;
209 
210         const float *rIn = in[rChannel] + rowX;
211         const float *gIn = in[gChannel] + rowX;
212         const float *bIn = in[bChannel] + rowX;
213 
214         float *rOut = out.writable(rChannel) + rowX;
215         float *gOut = out.writable(gChannel) + rowX;
216         float *bOut = out.writable(bChannel) + rowX;
217 
218         // OCIO modifies in-place
219         // Note: xOut can equal xIn in some circumstances, such as when the
220         // 'Black' (throwaway) scanline is uses. We thus must guard memcpy,
221         // which does not allow for overlapping regions.
222         if (rOut != rIn) memcpy(rOut, rIn, sizeof(float)*rowWidth);
223         if (gOut != gIn) memcpy(gOut, gIn, sizeof(float)*rowWidth);
224         if (bOut != bIn) memcpy(bOut, bIn, sizeof(float)*rowWidth);
225 
226         try
227         {
228             OCIO::PlanarImageDesc img(rOut, gOut, bOut, NULL, rowWidth, /*height*/ 1);
229             m_processor->apply(img);
230         }
231         catch(OCIO::Exception &e)
232         {
233             error(e.what());
234         }
235     }
236 }
237 
238 const DD::Image::Op::Description OCIOFileTransform::description("OCIOFileTransform", build);
239 
Class() const240 const char* OCIOFileTransform::Class() const
241 {
242     return description.name;
243 }
244 
displayName() const245 const char* OCIOFileTransform::displayName() const
246 {
247     return description.name;
248 }
249 
node_help() const250 const char* OCIOFileTransform::node_help() const
251 {
252     if(m_nodehelp.empty())
253     {
254         const char * helptext =
255         "Use OpenColorIO to apply a transform loaded from the given "
256         "file.\n\n"
257         "This is usually a 1D or 3D LUT file, but can be other file-based "
258         "transform, for example an ASC ColorCorrection XML file.\n\n"
259         "Note that the file's transform is applied with no special "
260         "input/output colorspace handling - so if the file expects "
261         "log-encoded pixels, but you apply the node to a linear "
262         "image, you will get incorrect results.\n\n";
263 
264         std::ostringstream os;
265         os << helptext;
266 
267         os << "Supported formats:\n";
268         for(int i=0; i<OCIO::FileTransform::getNumFormats(); ++i)
269         {
270             const char* name = OCIO::FileTransform::getFormatNameByIndex(i);
271             const char* exten = OCIO::FileTransform::getFormatExtensionByIndex(i);
272             os << "\n." << exten << " (" << name << ")";
273         }
274 
275         m_nodehelp = os.str();
276     }
277 
278     return m_nodehelp.c_str();
279 }
280 
281 
build(Node * node)282 DD::Image::Op* build(Node *node)
283 {
284     DD::Image::NukeWrapper *op = new DD::Image::NukeWrapper(new OCIOFileTransform(node));
285     op->channels(DD::Image::Mask_RGB);
286     return op;
287 }
288