PageRenderTime 52ms CodeModel.GetById 17ms app.highlight 29ms RepoModel.GetById 2ms app.codeStats 0ms

/src/nuke/OCIOColorSpace/OCIOColorSpace.cpp

http://github.com/imageworks/OpenColorIO
C++ | 376 lines | 293 code | 59 blank | 24 comment | 35 complexity | 29a363e5121d0fc36901be3a1d9058ae MD5 | raw file
  1/**
  2 * OpenColorIO ColorSpace Iop.
  3 */
  4
  5#include "OCIOColorSpace.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
 25OCIOColorSpace::OCIOColorSpace(Node *n) : DD::Image::PixelIop(n)
 26{
 27    m_hasColorSpaces = false;
 28
 29    m_inputColorSpaceIndex = 0;
 30    m_outputColorSpaceIndex = 0;
 31    
 32    // Query the color space names from the current config
 33    // TODO (when to) re-grab the list of available color spaces? How to save/load?
 34    
 35    try
 36    {
 37        OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
 38        
 39        OCIO::ConstColorSpaceRcPtr defaultcs = config->getColorSpace(OCIO::ROLE_SCENE_LINEAR);
 40        if(!defaultcs) throw std::runtime_error("ROLE_SCENE_LINEAR not defined.");
 41        std::string defaultColorSpaceName = defaultcs->getName();
 42        
 43        for(int i = 0; i < config->getNumColorSpaces(); i++)
 44        {
 45            std::string csname = config->getColorSpaceNameByIndex(i);
 46            
 47#ifdef OCIO_CASCADE
 48            std::string family = config->getColorSpace(csname.c_str())->getFamily();
 49            if(family.empty())
 50                m_colorSpaceNames.push_back(csname.c_str());
 51            else
 52                m_colorSpaceNames.push_back(family + "/" + csname);
 53#else
 54            m_colorSpaceNames.push_back(csname);
 55#endif
 56            
 57            if(csname == defaultColorSpaceName)
 58            {
 59                m_inputColorSpaceIndex = i;
 60                m_outputColorSpaceIndex = i;
 61            }
 62        }
 63    }
 64    catch (OCIO::Exception& e)
 65    {
 66        std::cerr << "OCIOColorSpace: " << e.what() << std::endl;
 67    }
 68    catch (...)
 69    {
 70        std::cerr << "OCIOColorSpace: Unknown exception during OCIO setup." << std::endl;
 71    }
 72    
 73    // Then, create a cstr array for passing to Nuke
 74    // This must be done in a second pass, lest the original m_colorSpaceNames
 75    // std::string be reallocated in the interim
 76    for(unsigned int i=0; i<m_colorSpaceNames.size(); ++i)
 77    {
 78        m_inputColorSpaceCstrNames.push_back(m_colorSpaceNames[i].c_str());
 79        m_outputColorSpaceCstrNames.push_back(m_colorSpaceNames[i].c_str());
 80    }
 81    
 82    m_inputColorSpaceCstrNames.push_back(NULL);
 83    m_outputColorSpaceCstrNames.push_back(NULL);
 84    
 85    m_hasColorSpaces = (!m_colorSpaceNames.empty());
 86    
 87    if(!m_hasColorSpaces)
 88    {
 89        std::cerr << "OCIOColorSpace: No color spaces available for input and/or output." << std::endl;
 90    }
 91}
 92
 93OCIOColorSpace::~OCIOColorSpace()
 94{
 95
 96}
 97
 98void OCIOColorSpace::knobs(DD::Image::Knob_Callback f)
 99{
100#ifdef OCIO_CASCADE
101    DD::Image::CascadingEnumeration_knob(f,
102        &m_inputColorSpaceIndex, &m_inputColorSpaceCstrNames[0], "in_colorspace", "in");
103    DD::Image::Tooltip(f, "Input data is taken to be in this color space.");
104    DD::Image::SetFlags(f, DD::Image::Knob::ALWAYS_SAVE);
105
106    DD::Image::CascadingEnumeration_knob(f,
107        &m_outputColorSpaceIndex, &m_outputColorSpaceCstrNames[0], "out_colorspace", "out");
108    DD::Image::Tooltip(f, "Image data is converted to this color space for output.");
109    DD::Image::SetFlags(f, DD::Image::Knob::ALWAYS_SAVE);
110#else
111    DD::Image::Enumeration_knob(f,
112        &m_inputColorSpaceIndex, &m_inputColorSpaceCstrNames[0], "in_colorspace", "in");
113    DD::Image::Tooltip(f, "Input data is taken to be in this color space.");
114    DD::Image::SetFlags(f, DD::Image::Knob::ALWAYS_SAVE);
115
116    DD::Image::Enumeration_knob(f,
117        &m_outputColorSpaceIndex, &m_outputColorSpaceCstrNames[0], "out_colorspace", "out");
118    DD::Image::Tooltip(f, "Image data is converted to this color space for output.");
119    DD::Image::SetFlags(f, DD::Image::Knob::ALWAYS_SAVE);
120#endif
121    
122}
123
124OCIO::ConstContextRcPtr OCIOColorSpace::getLocalContext()
125{
126    OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
127    OCIO::ConstContextRcPtr context = config->getCurrentContext();
128    OCIO::ContextRcPtr mutableContext;
129    
130    if(!m_contextKey1.empty())
131    {
132        if(!mutableContext) mutableContext = context->createEditableCopy();
133        mutableContext->setStringVar(m_contextKey1.c_str(), m_contextValue1.c_str());
134    }
135    if(!m_contextKey2.empty())
136    {
137        if(!mutableContext) mutableContext = context->createEditableCopy();
138        mutableContext->setStringVar(m_contextKey2.c_str(), m_contextValue2.c_str());
139    }
140    if(!m_contextKey3.empty())
141    {
142        if(!mutableContext) mutableContext = context->createEditableCopy();
143        mutableContext->setStringVar(m_contextKey3.c_str(), m_contextValue3.c_str());
144    }
145    if(!m_contextKey4.empty())
146    {
147        if(!mutableContext) mutableContext = context->createEditableCopy();
148        mutableContext->setStringVar(m_contextKey4.c_str(), m_contextValue4.c_str());
149    }
150    
151    if(mutableContext) context = mutableContext;
152    return context;
153}
154
155void OCIOColorSpace::append(DD::Image::Hash& localhash)
156{
157    // TODO: Hang onto the context, what if getting it
158    // (and querying getCacheID) is expensive?
159    try
160    {
161        OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
162        OCIO::ConstContextRcPtr context = getLocalContext();
163        std::string configCacheID = config->getCacheID(context);
164        localhash.append(configCacheID);
165    }
166    catch(OCIO::Exception &e)
167    {
168        error(e.what());
169        return;
170    }
171}
172
173void OCIOColorSpace::_validate(bool for_real)
174{
175    if(!m_hasColorSpaces)
176    {
177        error("No color spaces available for input and/or output.");
178        return;
179    }
180
181    int inputColorSpaceCount = static_cast<int>(m_inputColorSpaceCstrNames.size()) - 1;
182    if(m_inputColorSpaceIndex < 0 || m_inputColorSpaceIndex >= inputColorSpaceCount)
183    {
184        std::ostringstream err;
185        err << "Input color space index (" << m_inputColorSpaceIndex << ") out of range.";
186        error(err.str().c_str());
187        return;
188    }
189
190    int outputColorSpaceCount = static_cast<int>(m_outputColorSpaceCstrNames.size()) - 1;
191    if(m_outputColorSpaceIndex < 0 || m_outputColorSpaceIndex >= outputColorSpaceCount)
192    {
193        std::ostringstream err;
194        err << "Output color space index (" << m_outputColorSpaceIndex << ") out of range.";
195        error(err.str().c_str());
196        return;
197    }
198
199    try
200    {
201        OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
202        
203        const char * inputName = config->getColorSpaceNameByIndex(m_inputColorSpaceIndex);
204        const char * outputName = config->getColorSpaceNameByIndex(m_outputColorSpaceIndex);
205        
206        OCIO::ConstContextRcPtr context = getLocalContext();
207        m_processor = config->getProcessor(context, inputName, outputName);
208    }
209    catch(OCIO::Exception &e)
210    {
211        error(e.what());
212        return;
213    }
214    
215    if(m_processor->isNoOp())
216    {
217        set_out_channels(DD::Image::Mask_None); // prevents engine() from being called
218    } else {    
219        set_out_channels(DD::Image::Mask_All);
220    }
221
222    DD::Image::PixelIop::_validate(for_real);
223}
224
225// Note that this is copied by others (OCIODisplay)
226void OCIOColorSpace::in_channels(int /* n unused */, DD::Image::ChannelSet& mask) const
227{
228    DD::Image::ChannelSet done;
229    foreach(c, mask)
230    {
231        if (DD::Image::colourIndex(c) < 3 && !(done & c))
232        {
233            done.addBrothers(c, 3);
234        }
235    }
236    mask += done;
237}
238
239// See Saturation::pixel_engine for a well-commented example.
240// Note that this is copied by others (OCIODisplay)
241void OCIOColorSpace::pixel_engine(
242    const DD::Image::Row& in,
243    int /* rowY unused */, int rowX, int rowXBound,
244    DD::Image::ChannelMask outputChannels,
245    DD::Image::Row& out)
246{
247    int rowWidth = rowXBound - rowX;
248
249    DD::Image::ChannelSet done;
250    foreach (requestedChannel, outputChannels)
251    {
252        // Skip channels which had their trios processed already,
253        if (done & requestedChannel)
254        {
255            continue;
256        }
257
258        // Pass through channels which are not selected for processing
259        // and non-rgb channels.
260        if (colourIndex(requestedChannel) >= 3)
261        {
262            out.copy(in, requestedChannel, rowX, rowXBound);
263            continue;
264        }
265
266        DD::Image::Channel rChannel = DD::Image::brother(requestedChannel, 0);
267        DD::Image::Channel gChannel = DD::Image::brother(requestedChannel, 1);
268        DD::Image::Channel bChannel = DD::Image::brother(requestedChannel, 2);
269
270        done += rChannel;
271        done += gChannel;
272        done += bChannel;
273
274        const float *rIn = in[rChannel] + rowX;
275        const float *gIn = in[gChannel] + rowX;
276        const float *bIn = in[bChannel] + rowX;
277
278        float *rOut = out.writable(rChannel) + rowX;
279        float *gOut = out.writable(gChannel) + rowX;
280        float *bOut = out.writable(bChannel) + rowX;
281
282        // OCIO modifies in-place
283        // Note: xOut can equal xIn in some circumstances, such as when the
284        // 'Black' (throwaway) scanline is uses. We thus must guard memcpy,
285        // which does not allow for overlapping regions.
286        if (rOut != rIn) memcpy(rOut, rIn, sizeof(float)*rowWidth);
287        if (gOut != gIn) memcpy(gOut, gIn, sizeof(float)*rowWidth);
288        if (bOut != bIn) memcpy(bOut, bIn, sizeof(float)*rowWidth);
289
290        try
291        {
292            OCIO::PlanarImageDesc img(rOut, gOut, bOut, NULL, rowWidth, /*height*/ 1);
293            m_processor->apply(img);
294        }
295        catch(OCIO::Exception &e)
296        {
297            error(e.what());
298        }
299    }
300}
301
302const DD::Image::Op::Description OCIOColorSpace::description("OCIOColorSpace", build);
303
304const char* OCIOColorSpace::Class() const
305{
306    return description.name;
307}
308
309const char* OCIOColorSpace::displayName() const
310{
311    return description.name;
312}
313
314const char* OCIOColorSpace::node_help() const
315{
316    // TODO more detailed help text
317    return "Use OpenColorIO to convert from one color space to another.";
318}
319
320// This class is necessary in order to call knobsAtTheEnd(). Otherwise, the NukeWrapper knobs 
321// will be added to the Context tab instead of the primary tab.
322class OCIOColorSpaceNukeWrapper : public DD::Image::NukeWrapper
323{
324public:
325    OCIOColorSpaceNukeWrapper(DD::Image::PixelIop* op) : DD::Image::NukeWrapper(op)
326    {
327    }
328    
329    virtual void attach()
330    {
331        wrapped_iop()->attach();
332    }
333    
334    virtual void detach()
335    {
336        wrapped_iop()->detach();
337    }
338    
339    virtual void knobs(DD::Image::Knob_Callback f)
340    {
341        OCIOColorSpace* csIop = dynamic_cast<OCIOColorSpace*>(wrapped_iop());
342        if(!csIop) return;
343        
344        DD::Image::NukeWrapper::knobs(f);
345        
346        DD::Image::Tab_knob(f, "Context");
347        {
348            DD::Image::String_knob(f, &csIop->m_contextKey1, "key1");
349            DD::Image::Spacer(f, 10);
350            DD::Image::String_knob(f, &csIop->m_contextValue1, "value1");
351            DD::Image::ClearFlags(f, DD::Image::Knob::STARTLINE);
352
353            DD::Image::String_knob(f, &csIop->m_contextKey2, "key2");
354            DD::Image::Spacer(f, 10);
355            DD::Image::String_knob(f, &csIop->m_contextValue2, "value2");
356            DD::Image::ClearFlags(f, DD::Image::Knob::STARTLINE);
357
358            DD::Image::String_knob(f, &csIop->m_contextKey3, "key3");
359            DD::Image::Spacer(f, 10);
360            DD::Image::String_knob(f, &csIop->m_contextValue3, "value3");
361            DD::Image::ClearFlags(f, DD::Image::Knob::STARTLINE);
362
363            DD::Image::String_knob(f, &csIop->m_contextKey4, "key4");
364            DD::Image::Spacer(f, 10);
365            DD::Image::String_knob(f, &csIop->m_contextValue4, "value4");
366            DD::Image::ClearFlags(f, DD::Image::Knob::STARTLINE);
367        }
368    }
369};
370
371static DD::Image::Op* build(Node *node)
372{
373    DD::Image::NukeWrapper *op = (new OCIOColorSpaceNukeWrapper(new OCIOColorSpace(node)));
374    op->channels(DD::Image::Mask_RGB);
375    return op;
376}