PageRenderTime 51ms CodeModel.GetById 1ms app.highlight 46ms RepoModel.GetById 1ms app.codeStats 0ms

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