PageRenderTime 93ms CodeModel.GetById 28ms app.highlight 56ms RepoModel.GetById 1ms app.codeStats 0ms

/src/nuke/OCIODisplay/OCIODisplay.cpp

http://github.com/imageworks/OpenColorIO
C++ | 561 lines | 455 code | 82 blank | 24 comment | 63 complexity | 6b4363256ed468ab6c0ab9da4ffc6128 MD5 | raw file
  1/**
  2 * OpenColorIO Display Iop.
  3 */
  4
  5#include "OCIODisplay.h"
  6
  7namespace OCIO = OCIO_NAMESPACE;
  8
  9#include <algorithm>
 10#include <string>
 11#include <sstream>
 12#include <stdexcept>
 13
 14#include <DDImage/Channel.h>
 15#include <DDImage/PixelIop.h>
 16#include <DDImage/NukeWrapper.h>
 17#include <DDImage/Row.h>
 18#include <DDImage/Knobs.h>
 19#include <DDImage/Enumeration_KnobI.h>
 20#include <DDImage/ddImageVersionNumbers.h>
 21
 22// Should we use cascasing ColorSpace menus
 23#if defined kDDImageVersionInteger && (kDDImageVersionInteger>=62300)
 24#define OCIO_CASCADE
 25#endif
 26
 27OCIODisplay::OCIODisplay(Node *n) : DD::Image::PixelIop(n)
 28{
 29    m_layersToProcess = DD::Image::Mask_RGBA;
 30    m_hasLists = false;
 31    m_colorSpaceIndex = m_displayIndex = m_viewIndex = 0;
 32    m_displayKnob = m_viewKnob = NULL;
 33    m_gain = 1.0;
 34    m_gamma = 1.0;
 35    m_channel = 2;
 36    m_transform = OCIO::DisplayTransform::Create();
 37    
 38    try
 39    {
 40        OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
 41        
 42        OCIO::ConstColorSpaceRcPtr defaultcs = config->getColorSpace(OCIO::ROLE_SCENE_LINEAR);
 43        if(!defaultcs) throw std::runtime_error("ROLE_SCENE_LINEAR not defined.");
 44        std::string defaultColorSpaceName = defaultcs->getName();
 45        
 46        for(int i=0; i<config->getNumColorSpaces(); ++i)
 47        {
 48            std::string csname = config->getColorSpaceNameByIndex(i);
 49            
 50#ifdef OCIO_CASCADE
 51            std::string family = config->getColorSpace(csname.c_str())->getFamily();
 52            if(family.empty())
 53                m_colorSpaceNames.push_back(csname.c_str());
 54            else
 55                m_colorSpaceNames.push_back(family + "/" + csname);
 56#else
 57            m_colorSpaceNames.push_back(csname);
 58#endif
 59            
 60            if(defaultColorSpaceName == csname)
 61            {
 62                m_colorSpaceIndex = i;
 63            }
 64        }
 65        
 66        std::string defaultDisplay = config->getDefaultDisplay();
 67        
 68        for(int i=0; i<config->getNumDisplays(); ++i)
 69        {
 70            std::string display = config->getDisplay(i);
 71            m_displayNames.push_back(display);
 72            
 73            if(display == defaultDisplay)
 74            {
 75                m_displayIndex = i;
 76            }
 77        }
 78    }
 79    catch(OCIO::Exception& e)
 80    {
 81        std::cerr << "OCIODisplay: " << e.what() << std::endl;
 82    }
 83    catch(...)
 84    {
 85        std::cerr << "OCIODisplay: Unknown exception during OCIO setup." << std::endl;
 86    }
 87    
 88    // Build the cstr vectors on our second pass
 89    for(unsigned int i=0; i<m_colorSpaceNames.size(); ++i)
 90    {
 91        m_colorSpaceCstrNames.push_back(m_colorSpaceNames[i].c_str());
 92    }
 93    m_colorSpaceCstrNames.push_back(NULL);
 94    
 95    for(unsigned int i=0; i<m_displayNames.size(); ++i)
 96    {
 97        m_displayCstrNames.push_back(m_displayNames[i].c_str());
 98    }
 99    m_displayCstrNames.push_back(NULL);
100    
101    refreshDisplayTransforms();
102    
103    m_hasLists = !(m_colorSpaceNames.empty() || m_displayNames.empty() || m_viewNames.empty());
104    
105    if(!m_hasLists)
106    {
107        std::cerr << "OCIODisplay: Missing one or more of colorspaces, display devices, or display transforms." << std::endl;
108    }
109}
110
111OCIODisplay::~OCIODisplay()
112{
113
114}
115
116void OCIODisplay::knobs(DD::Image::Knob_Callback f)
117{
118#ifdef OCIO_CASCADE
119    DD::Image::CascadingEnumeration_knob(f,
120        &m_colorSpaceIndex, &m_colorSpaceCstrNames[0], "colorspace", "input colorspace");
121#else
122    DD::Image::Enumeration_knob(f,
123        &m_colorSpaceIndex, &m_colorSpaceCstrNames[0], "colorspace", "input colorspace");
124#endif
125    DD::Image::SetFlags(f, DD::Image::Knob::ALWAYS_SAVE);
126    DD::Image::Tooltip(f, "Input data is taken to be in this colorspace.");
127
128    m_displayKnob = DD::Image::Enumeration_knob(f,
129        &m_displayIndex, &m_displayCstrNames[0], "display", "display device");
130    DD::Image::SetFlags(f, DD::Image::Knob::ALWAYS_SAVE);
131    DD::Image::Tooltip(f, "Display device for output.");
132
133    m_viewKnob = DD::Image::Enumeration_knob(f,
134        &m_viewIndex, &m_viewCstrNames[0], "view", "view transform");
135    DD::Image::SetFlags(f, DD::Image::Knob::ALWAYS_SAVE);
136    DD::Image::Tooltip(f, "Display transform for output.");
137    
138    DD::Image::Float_knob(f, &m_gain, DD::Image::IRange(1.0 / 64.0f, 64.0f), "gain");
139    DD::Image::SetFlags(f, DD::Image::Knob::NO_ANIMATION | DD::Image::Knob::NO_UNDO | DD::Image::Knob::LOG_SLIDER);
140    DD::Image::Tooltip(f, "Exposure adjustment, in scene-linear, prior to the display transform.");
141    
142    DD::Image::Float_knob(f, &m_gamma, DD::Image::IRange(0, 4), "gamma");
143    DD::Image::SetFlags(f, DD::Image::Knob::NO_ANIMATION | DD::Image::Knob::NO_UNDO | DD::Image::Knob::LOG_SLIDER);
144    DD::Image::Tooltip(f, "Gamma correction applied after the display transform.");
145    
146    static const char* const channelvalues[] = {
147      "Luminance",
148      "Matte overlay",
149      "RGB",
150      "R",
151      "G",
152      "B",
153      "A",
154      0
155    };
156    DD::Image::Enumeration_knob(f, &m_channel, channelvalues, "channel_selector", "channel view");
157    DD::Image::SetFlags(f, DD::Image::Knob::NO_ANIMATION | DD::Image::Knob::NO_UNDO);
158    DD::Image::Tooltip(f, "Specify which channels to view (prior to the display transform).");
159    
160    DD::Image::Divider(f);
161    
162    DD::Image::Input_ChannelSet_knob(f, &m_layersToProcess, 0, "layer", "layer");
163    // DD::Image::SetFlags(f, DD::Image::Knob::NO_CHECKMARKS | DD::Image::Knob::NO_ALPHA_PULLDOWN);
164    DD::Image::Tooltip(f, "Set which layer to process. This should be a layer with rgb data.");
165    
166    DD::Image::Tab_knob(f, "Context");
167    {
168        DD::Image::String_knob(f, &m_contextKey1, "key1");
169        DD::Image::Spacer(f, 10);
170        DD::Image::String_knob(f, &m_contextValue1, "value1");
171        DD::Image::ClearFlags(f, DD::Image::Knob::STARTLINE);
172        
173        DD::Image::String_knob(f, &m_contextKey2, "key2");
174        DD::Image::Spacer(f, 10);
175        DD::Image::String_knob(f, &m_contextValue2, "value2");
176        DD::Image::ClearFlags(f, DD::Image::Knob::STARTLINE);
177        
178        DD::Image::String_knob(f, &m_contextKey3, "key3");
179        DD::Image::Spacer(f, 10);
180        DD::Image::String_knob(f, &m_contextValue3, "value3");
181        DD::Image::ClearFlags(f, DD::Image::Knob::STARTLINE);
182        
183        DD::Image::String_knob(f, &m_contextKey4, "key4");
184        DD::Image::Spacer(f, 10);
185        DD::Image::String_knob(f, &m_contextValue4, "value4");
186        DD::Image::ClearFlags(f, DD::Image::Knob::STARTLINE);
187    }
188}
189
190OCIO::ConstContextRcPtr OCIODisplay::getLocalContext()
191{
192    OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
193    OCIO::ConstContextRcPtr context = config->getCurrentContext();
194    OCIO::ContextRcPtr mutableContext;
195    
196    if(!m_contextKey1.empty())
197    {
198        if(!mutableContext) mutableContext = context->createEditableCopy();
199        mutableContext->setStringVar(m_contextKey1.c_str(), m_contextValue1.c_str());
200    }
201    if(!m_contextKey2.empty())
202    {
203        if(!mutableContext) mutableContext = context->createEditableCopy();
204        mutableContext->setStringVar(m_contextKey2.c_str(), m_contextValue2.c_str());
205    }
206    if(!m_contextKey3.empty())
207    {
208        if(!mutableContext) mutableContext = context->createEditableCopy();
209        mutableContext->setStringVar(m_contextKey3.c_str(), m_contextValue3.c_str());
210    }
211    if(!m_contextKey4.empty())
212    {
213        if(!mutableContext) mutableContext = context->createEditableCopy();
214        mutableContext->setStringVar(m_contextKey4.c_str(), m_contextValue4.c_str());
215    }
216    
217    if(mutableContext) context = mutableContext;
218    
219    return context;
220}
221
222void OCIODisplay::append(DD::Image::Hash& localhash)
223{
224    // TODO: Hang onto the context, what if getting it
225    // (and querying getCacheID) is expensive?
226    try
227    {
228        OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
229        OCIO::ConstContextRcPtr context = getLocalContext();
230        std::string configCacheID = config->getCacheID(context);
231        localhash.append(configCacheID);
232        
233        // This is required due to our custom channel overlay mode post-processing
234        localhash.append(m_channel);
235    }
236    catch(OCIO::Exception &e)
237    {
238        error(e.what());
239        return;
240    }
241}
242
243void OCIODisplay::_validate(bool for_real)
244{
245    input0().validate(for_real);
246
247    if(!m_hasLists)
248    {
249        error("Missing one or more of colorspaces, display devices, or display transforms.");
250        return;
251    }
252
253    int nColorSpaces = static_cast<int>(m_colorSpaceNames.size());
254    if(m_colorSpaceIndex < 0 || m_colorSpaceIndex >= nColorSpaces)
255    {
256        std::ostringstream err;
257        err << "ColorSpace index (" << m_colorSpaceIndex << ") out of range.";
258        error(err.str().c_str());
259        return;
260    }
261
262    try
263    {
264        OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
265        
266        m_transform->setInputColorSpaceName(config->getColorSpaceNameByIndex(m_colorSpaceIndex));
267        m_transform->setDisplay(m_displayCstrNames[m_displayIndex]);
268        m_transform->setView(m_viewCstrNames[m_viewIndex]);
269        
270        // Specify an (optional) linear color correction
271        {
272            float m44[16];
273            float offset4[4];
274            
275            const float slope4f[] = { m_gain, m_gain, m_gain, m_gain };
276            OCIO::MatrixTransform::Scale(m44, offset4, slope4f);
277            
278            OCIO::MatrixTransformRcPtr mtx =  OCIO::MatrixTransform::Create();
279            mtx->setValue(m44, offset4);
280            
281            m_transform->setLinearCC(mtx);
282        }
283        
284        // Specify an (optional) post-display transform.
285        {
286            float exponent = 1.0f/std::max(1e-6f, m_gamma);
287            const float exponent4f[] = { exponent, exponent, exponent, exponent };
288            OCIO::ExponentTransformRcPtr cc =  OCIO::ExponentTransform::Create();
289            cc->setValue(exponent4f);
290            m_transform->setDisplayCC(cc);
291        }
292        
293        // Add Channel swizzling
294        {
295            int channelHot[4] = { 0, 0, 0, 0};
296            
297            switch(m_channel)
298            {
299            case 0: // Luma
300                channelHot[0] = 1;
301                channelHot[1] = 1;
302                channelHot[2] = 1;
303                break;
304            case 1: //  Channel overlay mode. Do rgb, and then swizzle later
305                channelHot[0] = 1;
306                channelHot[1] = 1;
307                channelHot[2] = 1;
308                channelHot[3] = 1;
309                break;
310            case 2: // RGB
311                channelHot[0] = 1;
312                channelHot[1] = 1;
313                channelHot[2] = 1;
314                channelHot[3] = 1;
315                break;
316            case 3: // R
317                channelHot[0] = 1;
318                break;
319            case 4: // G
320                channelHot[1] = 1;
321                break;
322            case 5: // B
323                channelHot[2] = 1;
324                break;
325            case 6: // A
326                channelHot[3] = 1;
327                break;
328            default:
329                break;
330            }
331            
332            float lumacoef[3];
333            config->getDefaultLumaCoefs(lumacoef);
334            float m44[16];
335            float offset[4];
336            OCIO::MatrixTransform::View(m44, offset, channelHot, lumacoef);
337            OCIO::MatrixTransformRcPtr swizzle = OCIO::MatrixTransform::Create();
338            swizzle->setValue(m44, offset);
339            m_transform->setChannelView(swizzle);
340        }
341        
342        OCIO::ConstContextRcPtr context = getLocalContext();
343        m_processor = config->getProcessor(context,
344                                           m_transform,
345                                           OCIO::TRANSFORM_DIR_FORWARD);
346    }
347    catch(OCIO::Exception &e)
348    {
349        error(e.what());
350        return;
351    }
352    
353    if(m_processor->isNoOp())
354    {
355        set_out_channels(DD::Image::Mask_None); // prevents engine() from being called
356    } else {    
357        set_out_channels(DD::Image::Mask_All);
358    }
359
360    DD::Image::PixelIop::_validate(for_real);
361}
362
363// Note: Same as OCIO ColorSpace::in_channels.
364void OCIODisplay::in_channels(int /* n unused */, DD::Image::ChannelSet& mask) const
365{
366    DD::Image::ChannelSet done;
367    foreach(c, mask)
368    {
369        if ((m_layersToProcess & c) && DD::Image::colourIndex(c) < 4 && !(done & c))
370        {
371            done.addBrothers(c, 4);
372        }
373    }
374    mask += done;
375}
376
377// Note: Same as OCIO ColorSpace::pixel_engine.
378void OCIODisplay::pixel_engine(
379    const DD::Image::Row& in,
380    int /* rowY unused */, int rowX, int rowXBound,
381    DD::Image::ChannelMask outputChannels,
382    DD::Image::Row& out)
383{
384    int rowWidth = rowXBound - rowX;
385
386    DD::Image::ChannelSet done;
387    foreach (requestedChannel, outputChannels)
388    {
389        // Skip channels which had their trios processed already,
390        if (done & requestedChannel)
391        {
392            continue;
393        }
394
395        // Pass through channels which are not selected for processing
396        // and non-rgb channels.
397        if (!(m_layersToProcess & requestedChannel))
398        {
399            out.copy(in, requestedChannel, rowX, rowXBound);
400            continue;
401        }
402
403        DD::Image::Channel rChannel = DD::Image::brother(requestedChannel, 0);
404        DD::Image::Channel gChannel = DD::Image::brother(requestedChannel, 1);
405        DD::Image::Channel bChannel = DD::Image::brother(requestedChannel, 2);
406        DD::Image::Channel aChannel = outputChannels.next(bChannel);
407
408        done += rChannel;
409        done += gChannel;
410        done += bChannel;
411        done += aChannel;
412
413        const float *rIn = in[rChannel] + rowX;
414        const float *gIn = in[gChannel] + rowX;
415        const float *bIn = in[bChannel] + rowX;
416        const float *aIn = in[aChannel] + rowX;
417
418        float *rOut = out.writable(rChannel) + rowX;
419        float *gOut = out.writable(gChannel) + rowX;
420        float *bOut = out.writable(bChannel) + rowX;
421        float *aOut = out.writable(aChannel) + rowX;
422
423        // OCIO modifies in-place
424        // Note: xOut can equal xIn in some circumstances, such as when the
425        // 'Black' (throwaway) scanline is uses. We thus must guard memcpy,
426        // which does not allow for overlapping regions.
427        if (rOut != rIn) memcpy(rOut, rIn, sizeof(float)*rowWidth);
428        if (gOut != gIn) memcpy(gOut, gIn, sizeof(float)*rowWidth);
429        if (bOut != bIn) memcpy(bOut, bIn, sizeof(float)*rowWidth);
430        if (aOut != aIn) memcpy(aOut, aIn, sizeof(float)*rowWidth);
431
432        try
433        {
434            OCIO::PlanarImageDesc img(rOut, gOut, bOut, aOut, rowWidth, /*height*/ 1);
435            m_processor->apply(img);
436        }
437        catch(OCIO::Exception &e)
438        {
439            error(e.what());
440        }
441        
442        // Hack to emulate Channel overlay mode
443        if(m_channel == 1)
444        {
445            for(int i=0; i<rowWidth; ++i)
446            {
447                rOut[i] = rOut[i] + (1.0f - rOut[i]) * (0.5f * aOut[i]);
448                gOut[i] = gOut[i] - gOut[i] * (0.5f * aOut[i]);
449                bOut[i] = bOut[i] - bOut[i] * (0.5f * aOut[i]);
450            }
451        }
452    }
453}
454
455void OCIODisplay::refreshDisplayTransforms()
456{
457    OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
458
459    if (m_displayIndex < 0 || m_displayIndex >= static_cast<int>(m_displayNames.size()))
460    {
461        std::ostringstream err;
462        err << "No or invalid display specified (index " << m_displayIndex << ").";
463        std::cerr << err.str(); // This can't set an error, as it's called in the constructor
464        return;
465    }
466
467    const char * display = m_displayCstrNames[m_displayIndex];
468    int numViews = config->getNumViews(display);
469    std::string defaultViewName = config->getDefaultView(display);
470
471    // Try to maintain an old transform name, or use the default.
472    bool hasOldViewName = false;
473    std::string oldViewName = "";
474    if (m_viewIndex >= 0 && m_viewIndex < static_cast<int>(m_viewNames.size()))
475    {
476        hasOldViewName = true;
477        oldViewName = m_viewNames[m_viewIndex];
478    }
479    int defaultViewIndex = 0, newViewIndex = -1;
480
481    m_viewCstrNames.clear();
482    m_viewNames.clear();
483
484    for(int i = 0; i < numViews; i++)
485    {
486        std::string view = config->getView(display, i);
487        m_viewNames.push_back(view);
488        if (hasOldViewName && view == oldViewName)
489        {
490            newViewIndex = i;
491        }
492        if (view == defaultViewName)
493        {
494            defaultViewIndex = i;
495        }
496    }
497    
498    for(unsigned int i=0; i<m_viewNames.size(); ++i)
499    {
500        m_viewCstrNames.push_back(m_viewNames[i].c_str());
501    }
502    m_viewCstrNames.push_back(NULL);
503    
504    if (newViewIndex == -1)
505    {
506        newViewIndex = defaultViewIndex;
507    }
508
509    if (m_viewKnob == NULL)
510    {
511        m_viewIndex = newViewIndex;
512    }
513    else
514    {
515        DD::Image::Enumeration_KnobI *enumKnob = m_viewKnob->enumerationKnob();
516        enumKnob->menu(m_viewNames);
517        m_viewKnob->set_value(newViewIndex);
518    }
519}
520
521int OCIODisplay::knob_changed(DD::Image::Knob *k)
522{
523    if (k == m_displayKnob && m_displayKnob != NULL)
524    {
525        refreshDisplayTransforms();
526        return 1;
527    }
528    else
529    {
530        return 0;
531    }
532}
533
534const DD::Image::Op::Description OCIODisplay::description("OCIODisplay", build);
535
536const char* OCIODisplay::Class() const
537{
538    return description.name;
539}
540
541const char* OCIODisplay::displayName() const
542{
543    return description.name;
544}
545
546const char* OCIODisplay::node_help() const
547{
548    // TODO more detailed help text
549    return "Use OpenColorIO to convert for a display device.";
550}
551
552
553DD::Image::Op* build(Node *node)
554{
555    DD::Image::NukeWrapper *op = new DD::Image::NukeWrapper(new OCIODisplay(node));
556    op->noMix();
557    op->noMask();
558    op->noChannels(); // prefer our own channels control without checkboxes / alpha
559    op->noUnpremult();
560    return op;
561}