PageRenderTime 60ms CodeModel.GetById 16ms app.highlight 39ms RepoModel.GetById 1ms app.codeStats 0ms

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