1 /* ***** BEGIN LICENSE BLOCK *****
2  * This file is part of openfx-io <https://github.com/MrKepzie/openfx-io>,
3  * Copyright (C) 2013-2018 INRIA
4  *
5  * openfx-io is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * openfx-io is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with openfx-io.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
17  * ***** END LICENSE BLOCK ***** */
18 
19 /*
20  * OFX GenericOCIO plugin add-on.
21  * Adds OpenColorIO functionality to any plugin.
22  */
23 
24 #include "GenericOCIO.h"
25 
26 /*
27    http://opencolorio.org/userguide/config_syntax.html#roles
28 
29    A description of all roles. Note that applications may interpret or use these differently.
30 
31    color_picking - Colors in a color-selection UI can be displayed in this space, while selecting colors in a different working space (e.g scene_linear or texture_paint)
32    color_timing - colorspace used for applying color corrections, e.g user-specified grade within an image viewer (if the application uses the DisplayTransform::setDisplayCC API method)
33    compositing_log - a log colorspace used for certain processing operations (plate resizing, pulling keys, degrain, etc). Used by the OCIOLogConvert Nuke node.
34    data - used when writing data outputs such as normals, depth data, and other “non color” data. The colorspace in this role should typically have data: true specified, so no color transforms are applied.
35    default - when strictparsing: false, this colorspace is used as a fallback. If not defined, the scene_linear role is used
36    matte_paint - Colorspace which matte-paintings are created in (for more information, see the guide on baking ICC profiles for Photoshop, and spi-vfx)
37    reference - Colorspace used for reference imagery (e.g sRGB images from the internet)
38    scene_linear - The scene-referred linear-to-light colorspace, typically used as reference space (see Terminology)
39    texture_paint - Similar to matte_paint but for painting textures for 3D objects (see the description of texture painting in SPI’s pipeline)
40  */
41 
42 #include <cstring>
43 #include <cstdlib>
44 #ifdef DEBUG
45 #include <cstdio>
46 #define DBG(x) x
47 #else
48 #define DBG(x) (void)0
49 #endif
50 #include <string>
51 #include <stdexcept>
52 #include <ofxsParam.h>
53 #include <ofxsImageEffect.h>
54 #include <ofxsLog.h>
55 #include <ofxNatron.h>
56 #include "ofxsMacros.h"
57 
58 #ifdef OFX_IO_USING_OCIO
59 #include <OpenColorIO/OpenColorIO.h>
60 namespace OCIO = OCIO_NAMESPACE;
61 #endif
62 
63 using std::string;
64 
65 NAMESPACE_OFX_ENTER
66     NAMESPACE_OFX_IO_ENTER
67 
68 #ifdef OFX_IO_USING_OCIO
69 static bool gWasOCIOEnvVarFound = false;
70 static bool gHostIsNatron   = false;
71 #endif
72 
73 
74 // define to disable hiding parameters (useful for debugging)
75 //#define OFX_OCIO_NOSECRET
76 
77 static string
trim(string const & str)78 trim(string const & str)
79 {
80     const string whitespace = " \t\f\v\n\r";
81 
82     std::size_t first = str.find_first_not_of(whitespace);
83 
84     // If there is no non-whitespace character, both first and last will be string::npos (-1)
85     // There is no point in checking both, since if either doesn't work, the
86     // other won't work, either.
87     if (first == string::npos) {
88         return "";
89     }
90 
91     std::size_t last  = str.find_last_not_of(whitespace);
92 
93     return str.substr(first, last - first + 1);
94 }
95 
96 static string
whitespacify(string str)97 whitespacify(string str)
98 {
99     std::replace( str.begin(), str.end(), '\t', ' ');
100     std::replace( str.begin(), str.end(), '\f', ' ');
101     std::replace( str.begin(), str.end(), '\v', ' ');
102     std::replace( str.begin(), str.end(), '\n', ' ');
103     std::replace( str.begin(), str.end(), '\r', ' ');
104 
105     return str;
106 }
107 
108 #ifdef OFX_IO_USING_OCIO
109 static const char*
colorSpaceName(OCIO::ConstConfigRcPtr config,const char * colorSpaceNameDefault)110 colorSpaceName(OCIO::ConstConfigRcPtr config,
111                const char* colorSpaceNameDefault)
112 {
113     OpenColorIO::ConstColorSpaceRcPtr cs;
114     if ( !strcmp(colorSpaceNameDefault, "sRGB") || !strcmp(colorSpaceNameDefault, "srgb") ) {
115         if ( ( cs = config->getColorSpace("sRGB") ) ) {
116             // nuke-default, blender, natron
117             return cs->getName();
118         } else if ( ( cs = config->getColorSpace("sRGB D65") ) ) {
119             // blender-cycles
120             return cs->getName();
121         } else if ( ( cs = config->getColorSpace("sRGB (D60 sim.)") ) ) {
122             // out_srgbd60sim or "sRGB (D60 sim.)" in aces 1.0.0
123             return cs->getName();
124         } else if ( ( cs = config->getColorSpace("out_srgbd60sim") ) ) {
125             // out_srgbd60sim or "sRGB (D60 sim.)" in aces 1.0.0
126             return cs->getName();
127         } else if ( ( cs = config->getColorSpace("rrt_Gamma2.2") ) ) {
128             // rrt_Gamma2.2 in aces 0.7.1
129             return cs->getName();
130         } else if ( ( cs = config->getColorSpace("rrt_srgb") ) ) {
131             // rrt_srgb in aces 0.1.1
132             return cs->getName();
133         } else if ( ( cs = config->getColorSpace("srgb8") ) ) {
134             // srgb8 in spi-vfx
135             return cs->getName();
136         } else if ( ( cs = config->getColorSpace("vd16") ) ) {
137             // vd16 in spi-anim
138             return cs->getName();
139         } else if ( ( cs = config->getColorSpace("VD16") ) ) {
140             // VD16 in blender
141             return cs->getName();
142         }
143     } else if ( !strcmp(colorSpaceNameDefault, "AdobeRGB") || !strcmp(colorSpaceNameDefault, "adobergb") ) {
144         if ( ( cs = config->getColorSpace("AdobeRGB") ) ) {
145             // natron
146             return cs->getName();
147         }
148     } else if ( !strcmp(colorSpaceNameDefault, "Rec709") || !strcmp(colorSpaceNameDefault, "rec709") ) {
149         if ( ( cs = config->getColorSpace("Rec709") ) ) {
150             // nuke-default
151             return cs->getName();
152         } else if ( ( cs = config->getColorSpace("nuke_rec709") ) ) {
153             // blender
154             return cs->getName();
155         } else if ( ( cs = config->getColorSpace("Rec.709 - Full") ) ) {
156             // out_rec709full or "Rec.709 - Full" in aces 1.0.0
157             return cs->getName();
158         } else if ( ( cs = config->getColorSpace("out_rec709full") ) ) {
159             // out_rec709full or "Rec.709 - Full" in aces 1.0.0
160             return cs->getName();
161         } else if ( ( cs = config->getColorSpace("rrt_rec709_full_100nits") ) ) {
162             // rrt_rec709_full_100nits in aces 0.7.1
163             return cs->getName();
164         } else if ( ( cs = config->getColorSpace("rrt_rec709") ) ) {
165             // rrt_rec709 in aces 0.1.1
166             return cs->getName();
167         } else if ( ( cs = config->getColorSpace("hd10") ) ) {
168             // hd10 in spi-anim and spi-vfx
169             return cs->getName();
170         }
171     } else if ( !strcmp(colorSpaceNameDefault, "KodakLog") || !strcmp(colorSpaceNameDefault, "kodaklog") ) {
172         if ( ( cs = config->getColorSpace("Cineon") ) ) {
173             // Cineon in nuke-default
174             return cs->getName();
175         } else if ( ( cs = config->getColorSpace("REDlogFilm") ) ) {
176             // REDlogFilm in aces 1.0.0
177             return cs->getName();
178         } else if ( ( cs = config->getColorSpace("cineon") ) ) {
179             // cineon in aces 0.7.1
180             return cs->getName();
181         } else if ( ( cs = config->getColorSpace("adx10") ) ) {
182             // adx10 in aces 0.1.1
183             return cs->getName();
184         } else if ( ( cs = config->getColorSpace("lg10") ) ) {
185             // lg10 in spi-vfx
186             return cs->getName();
187         } else if ( ( cs = config->getColorSpace("lm10") ) ) {
188             // lm10 in spi-anim
189             return cs->getName();
190         } else {
191             return OCIO::ROLE_COMPOSITING_LOG; // reasonable default
192         }
193     } else if ( !strcmp(colorSpaceNameDefault, "Linear") || !strcmp(colorSpaceNameDefault, "linear") ) {
194         return OCIO::ROLE_SCENE_LINEAR;
195         // lnf in spi-vfx
196     } else if ( ( cs = config->getColorSpace(colorSpaceNameDefault) ) ) {
197         // maybe we're lucky
198         return cs->getName();
199     }
200 
201     // unlucky
202     return colorSpaceNameDefault;
203 } // colorSpaceName
204 
205 static string
canonicalizeColorSpace(OCIO::ConstConfigRcPtr config,const string & csname)206 canonicalizeColorSpace(OCIO::ConstConfigRcPtr config,
207                        const string &csname)
208 {
209     if (!config) {
210         return csname;
211     }
212     const int defaultcs = config->getIndexForColorSpace(OCIO::ROLE_DEFAULT);
213     const int referencecs = config->getIndexForColorSpace(OCIO::ROLE_REFERENCE);
214     const int datacs = config->getIndexForColorSpace(OCIO::ROLE_DATA);
215     const int colorpickingcs = config->getIndexForColorSpace(OCIO::ROLE_COLOR_PICKING);
216     const int scenelinearcs = config->getIndexForColorSpace(OCIO::ROLE_SCENE_LINEAR);
217     const int compositinglogcs = config->getIndexForColorSpace(OCIO::ROLE_COMPOSITING_LOG);
218     const int colortimingcs = config->getIndexForColorSpace(OCIO::ROLE_COLOR_TIMING);
219     const int texturepaintcs = config->getIndexForColorSpace(OCIO::ROLE_TEXTURE_PAINT);
220     const int mattepaintcs = config->getIndexForColorSpace(OCIO::ROLE_MATTE_PAINT);
221     int inputSpaceIndex = config->getIndexForColorSpace( csname.c_str() );
222     if (inputSpaceIndex == scenelinearcs) {
223         return OCIO::ROLE_SCENE_LINEAR;
224     } else if (inputSpaceIndex == defaultcs) {
225         return OCIO::ROLE_DEFAULT;
226     } else if (inputSpaceIndex == referencecs) {
227         return OCIO::ROLE_REFERENCE;
228     } else if (inputSpaceIndex == datacs) {
229         return OCIO::ROLE_DATA;
230     } else if (inputSpaceIndex == colorpickingcs) {
231         return OCIO::ROLE_COLOR_PICKING;
232     } else if (inputSpaceIndex == compositinglogcs) {
233         return OCIO::ROLE_COMPOSITING_LOG;
234     } else if (inputSpaceIndex == colortimingcs) {
235         return OCIO::ROLE_COLOR_TIMING;
236     } else if (inputSpaceIndex == texturepaintcs) {
237         return OCIO::ROLE_TEXTURE_PAINT;
238     } else if (inputSpaceIndex == mattepaintcs) {
239         return OCIO::ROLE_MATTE_PAINT;
240     }
241 
242     return csname;
243 }
244 
245 #endif // ifdef OFX_IO_USING_OCIO
246 
GenericOCIO(ImageEffect * parent)247 GenericOCIO::GenericOCIO(ImageEffect* parent)
248     : _parent(parent)
249     , _created(false)
250 #ifdef OFX_IO_USING_OCIO
251     , _ocioConfigFileName()
252     , _ocioConfigFile(NULL)
253     , _inputSpace(NULL)
254     , _outputSpace(NULL)
255 #ifdef OFX_OCIO_CHOICE
256     , _choiceIsOk(true)
257     , _choiceFileName()
258     , _inputSpaceChoice(NULL)
259     , _outputSpaceChoice(NULL)
260 #endif
261     , _contextKey1(NULL)
262     , _contextValue1(NULL)
263     , _contextKey2(NULL)
264     , _contextValue2(NULL)
265     , _contextKey3(NULL)
266     , _contextValue3(NULL)
267     , _contextKey4(NULL)
268     , _contextValue4(NULL)
269     , _config()
270 #endif
271 {
272 #ifdef OFX_IO_USING_OCIO
273     _ocioConfigFile = _parent->fetchStringParam(kOCIOParamConfigFile);
274     if ( _parent->paramExists(kOCIOParamInputSpace) ) {
275         _inputSpace = _parent->fetchStringParam(kOCIOParamInputSpace);
276     }
277     if ( _parent->paramExists(kOCIOParamOutputSpace) ) {
278         _outputSpace = _parent->fetchStringParam(kOCIOParamOutputSpace);
279     }
280 #ifdef OFX_OCIO_CHOICE
281     _ocioConfigFile->getDefault(_choiceFileName);
282     if (_inputSpace) {
283         _inputSpaceChoice = _parent->fetchChoiceParam(kOCIOParamInputSpaceChoice);
284     }
285     if (_outputSpace) {
286         _outputSpaceChoice = _parent->fetchChoiceParam(kOCIOParamOutputSpaceChoice);
287     }
288 #endif
289     loadConfig();
290 #ifdef OFX_OCIO_CHOICE
291     if (!_config) {
292 #     ifndef OFX_OCIO_NOSECRET
293         if (_inputSpace) {
294             _inputSpaceChoice->setIsSecretAndDisabled(true);
295         }
296         if (_outputSpace) {
297             _outputSpaceChoice->setIsSecretAndDisabled(true);
298         }
299 #     endif
300     }
301 #endif
302     if ( _parent->paramExists(kOCIOParamContextKey1) ) {
303         _contextKey1 = _parent->fetchStringParam(kOCIOParamContextKey1);
304         _contextValue1 = _parent->fetchStringParam(kOCIOParamContextValue1);
305         _contextKey2 = _parent->fetchStringParam(kOCIOParamContextKey2);
306         _contextValue2 = _parent->fetchStringParam(kOCIOParamContextValue2);
307         _contextKey3 = _parent->fetchStringParam(kOCIOParamContextKey3);
308         _contextValue3 = _parent->fetchStringParam(kOCIOParamContextValue3);
309         _contextKey4 = _parent->fetchStringParam(kOCIOParamContextKey4);
310         _contextValue4 = _parent->fetchStringParam(kOCIOParamContextValue4);
311         assert(_contextKey1 && _contextKey2 && _contextKey3 && _contextKey4);
312         assert(_contextValue1 && _contextValue2 && _contextValue3 && _contextValue4);
313     }
314 #endif
315     // setup the GUI
316     // setValue() may be called from createInstance, according to
317     // http://openfx.sourceforge.net/Documentation/1.3/ofxProgrammingReference.html#SettingParams
318     inputCheck(0.);
319     outputCheck(0.);
320     _created = true;
321 }
322 
323 #ifdef OFX_IO_USING_OCIO
324 #ifdef OFX_OCIO_CHOICE
325 
326 // ChoiceParamType may be ChoiceParamDescriptor or ChoiceParam
327 template <typename ChoiceParamType>
328 static void
buildChoiceMenu(OCIO::ConstConfigRcPtr config,ChoiceParamType * choice,bool cascading,const string & name="")329 buildChoiceMenu(OCIO::ConstConfigRcPtr config,
330                 ChoiceParamType* choice,
331                 bool cascading,
332                 const string& name = "")
333 {
334     //DBG(std::printf("%p->resetOptions\n", (void*)choice));
335     choice->resetOptions();
336     assert(choice->getNOptions() == 0);
337     if (!config) {
338         return;
339     }
340     int def = -1;
341     int defaultcs = config->getIndexForColorSpace(OCIO::ROLE_DEFAULT);
342     int referencecs = config->getIndexForColorSpace(OCIO::ROLE_REFERENCE);
343     int datacs = config->getIndexForColorSpace(OCIO::ROLE_DATA);
344     int colorpickingcs = config->getIndexForColorSpace(OCIO::ROLE_COLOR_PICKING);
345     int scenelinearcs = config->getIndexForColorSpace(OCIO::ROLE_SCENE_LINEAR);
346     int compositinglogcs = config->getIndexForColorSpace(OCIO::ROLE_COMPOSITING_LOG);
347     int colortimingcs = config->getIndexForColorSpace(OCIO::ROLE_COLOR_TIMING);
348     int texturepaintcs = config->getIndexForColorSpace(OCIO::ROLE_TEXTURE_PAINT);
349     int mattepaintcs = config->getIndexForColorSpace(OCIO::ROLE_MATTE_PAINT);
350     for (int i = 0; i < config->getNumColorSpaces(); ++i) {
351         string csname = config->getColorSpaceNameByIndex(i);
352         string msg;
353         // set the default value, in case the GUI uses it
354         if ( !name.empty() && (csname == name) ) {
355             def = i;
356         }
357         OCIO::ConstColorSpaceRcPtr cs = config->getColorSpace( csname.c_str() );
358         if (cascading) {
359             string family = config->getColorSpace( csname.c_str() )->getFamily();
360             if ( !family.empty() ) {
361                 csname = family + "/" + csname;
362             }
363         }
364         string csdesc = cs ? cs->getDescription() : "(no colorspace)";
365         csdesc = whitespacify( trim(csdesc) );
366         int csdesclen = csdesc.size();
367         if (csdesclen > 0) {
368             msg += csdesc;
369         }
370         bool first = true;
371         int roles = 0;
372         if (i == defaultcs) {
373             msg += first ? " (" : ", ";
374             msg += OCIO::ROLE_DEFAULT;
375             first = false;
376             ++roles;
377         }
378         if (i == referencecs) {
379             msg += first ? " (" : ", ";
380             msg += OCIO::ROLE_REFERENCE;
381             first = false;
382             ++roles;
383         }
384         if (i == datacs) {
385             msg += first ? " (" : ", ";
386             msg += OCIO::ROLE_DATA;
387             first = false;
388             ++roles;
389         }
390         if (i == colorpickingcs) {
391             msg += first ? " (" : ", ";
392             msg += OCIO::ROLE_COLOR_PICKING;
393             first = false;
394             ++roles;
395         }
396         if (i == scenelinearcs) {
397             msg += first ? " (" : ", ";
398             msg += OCIO::ROLE_SCENE_LINEAR;
399             first = false;
400             ++roles;
401         }
402         if (i == compositinglogcs) {
403             msg += first ? " (" : ", ";
404             msg += OCIO::ROLE_COMPOSITING_LOG;
405             first = false;
406             ++roles;
407         }
408         if (i == colortimingcs) {
409             msg += first ? " (" : ", ";
410             msg += OCIO::ROLE_COLOR_TIMING;
411             first = false;
412             ++roles;
413         }
414         if (i == texturepaintcs) {
415             msg += first ? " (" : ", ";
416             msg += OCIO::ROLE_TEXTURE_PAINT;
417             first = false;
418             ++roles;
419         }
420         if (i == mattepaintcs) {
421             msg += first ? " (" : ", ";
422             msg += OCIO::ROLE_MATTE_PAINT;
423             first = false;
424             ++roles;
425         }
426         if (roles > 0) {
427             msg += ')';
428         }
429         //DBG(printf("%p->appendOption(\"%s\",\"%s\") (%d->%d options)\n", (void*)choice, csname.c_str(), msg.c_str(), i, i+1));
430         assert(choice->getNOptions() == i);
431         choice->appendOption(csname, msg);
432         assert(choice->getNOptions() == i + 1);
433     }
434     if (def != -1) {
435         choice->setDefault(def);
436     }
437 } // buildChoiceMenu
438 
439 #endif // ifdef OFX_OCIO_CHOICE
440 #endif // ifdef OFX_IO_USING_OCIO
441 
442 void
loadConfig()443 GenericOCIO::loadConfig()
444 {
445 #ifdef OFX_IO_USING_OCIO
446     string filename;
447     _ocioConfigFile->getValue(filename);
448 
449     if (filename == _ocioConfigFileName) {
450         return;
451     }
452     _config.reset();
453     try {
454         _ocioConfigFileName = filename;
455         _config = OCIO::Config::CreateFromFile( _ocioConfigFileName.c_str() );
456     } catch (OCIO::Exception &e) {
457         _ocioConfigFileName.clear();
458         if (_inputSpace) {
459             _inputSpace->setEnabled(false);
460 #         ifdef OFX_OCIO_CHOICE
461             _inputSpaceChoice->setEnabled(false);
462 #         endif
463         }
464         if (_outputSpace) {
465             _outputSpace->setEnabled(false);
466 #         ifdef OFX_OCIO_CHOICE
467             _outputSpaceChoice->setEnabled(false);
468 #         endif
469         }
470     }
471 #ifdef OFX_OCIO_CHOICE
472     if (_config) {
473         if (gHostIsNatron) {
474             // the choice menu can only be modified in Natron
475             // Natron supports changing the entries in a choiceparam
476             // Nuke (at least up to 8.0v3) does not
477             if (_inputSpace) {
478                 buildChoiceMenu( _config, _inputSpaceChoice, _inputSpaceChoice->getIsCascading() );
479             }
480             if (_outputSpace) {
481                 buildChoiceMenu( _config, _outputSpaceChoice, _outputSpaceChoice->getIsCascading() );
482             }
483             _choiceFileName = _ocioConfigFileName;
484         }
485         _choiceIsOk = (_ocioConfigFileName == _choiceFileName);
486         // do not set values during CreateInstance!!
487         ////inputCheck(); // may set values
488         ////outputCheck(); // may set values
489     }
490 #endif
491 #endif
492 } // GenericOCIO::loadConfig
493 
494 bool
configIsDefault() const495 GenericOCIO::configIsDefault() const
496 {
497 #ifdef OFX_IO_USING_OCIO
498     string filename;
499     _ocioConfigFile->getValue(filename);
500     string defaultFilename;
501     _ocioConfigFile->getDefault(defaultFilename);
502 
503     return (filename == defaultFilename);
504 #else
505 
506     return true;
507 #endif
508 }
509 
510 #ifdef OFX_IO_USING_OCIO
511 OCIO::ConstContextRcPtr
getLocalContext(double time) const512 GenericOCIO::getLocalContext(double time) const
513 {
514     OCIO::ConstContextRcPtr context = _config->getCurrentContext();
515     OCIO::ContextRcPtr mutableContext;
516 
517     if (_contextKey1) {
518         string contextKey1;
519         _contextKey1->getValueAtTime(time, contextKey1);
520         if ( !contextKey1.empty() ) {
521             string contextValue1;
522             _contextValue1->getValueAtTime(time, contextValue1);
523 
524             if (!mutableContext) {
525                 mutableContext = context->createEditableCopy();
526             }
527             mutableContext->setStringVar( contextKey1.c_str(), contextValue1.c_str() );
528         }
529     }
530     if (_contextKey2) {
531         string contextKey2;
532         _contextKey2->getValueAtTime(time, contextKey2);
533         if ( !contextKey2.empty() ) {
534             string contextValue2;
535             _contextValue2->getValueAtTime(time, contextValue2);
536 
537             if (!mutableContext) {
538                 mutableContext = context->createEditableCopy();
539             }
540             mutableContext->setStringVar( contextKey2.c_str(), contextValue2.c_str() );
541         }
542     }
543     if (_contextKey3) {
544         string contextKey3;
545         _contextKey3->getValueAtTime(time, contextKey3);
546         if ( !contextKey3.empty() ) {
547             string contextValue3;
548             _contextValue3->getValueAtTime(time, contextValue3);
549 
550             if (!mutableContext) {
551                 mutableContext = context->createEditableCopy();
552             }
553             mutableContext->setStringVar( contextKey3.c_str(), contextValue3.c_str() );
554         }
555     }
556     if (_contextKey4) {
557         string contextKey4;
558         _contextKey4->getValueAtTime(time, contextKey4);
559         if ( !contextKey4.empty() ) {
560             string contextValue4;
561             _contextValue4->getValueAtTime(time, contextValue4);
562 
563             if (!mutableContext) {
564                 mutableContext = context->createEditableCopy();
565             }
566             mutableContext->setStringVar( contextKey4.c_str(), contextValue4.c_str() );
567         }
568     }
569 
570     if (mutableContext) {
571         context = mutableContext;
572     }
573 
574     return context;
575 } // GenericOCIO::getLocalContext
576 
577 #endif // ifdef OFX_IO_USING_OCIO
578 
579 bool
isIdentity(double time) const580 GenericOCIO::isIdentity(double time) const
581 {
582     assert(_created);
583 #ifdef OFX_IO_USING_OCIO
584     if (!_config) {
585         string filename;
586         _ocioConfigFile->getValue(filename);
587         _parent->setPersistentMessage( Message::eMessageError, "", "Invalid OCIO config. file \"" + filename + "\"" );
588         throwSuiteStatusException(kOfxStatFailed);
589 
590         return false;
591     }
592     string inputSpace;
593     getInputColorspaceAtTime(time, inputSpace);
594     string outputSpace;
595     getOutputColorspaceAtTime(time, outputSpace);
596     if (inputSpace == outputSpace) {
597         return true;
598     }
599     try {
600         // maybe the names are not the same, but it's still a no-op (e.g. "scene_linear" and "linear")
601         OCIO::ConstContextRcPtr context = getLocalContext(time);//_config->getCurrentContext();
602         OCIO::ConstProcessorRcPtr proc = _config->getProcessor( context, inputSpace.c_str(), outputSpace.c_str() );
603 
604         return proc->isNoOp();
605     } catch (const std::exception& e) {
606         _parent->setPersistentMessage( Message::eMessageError, "", e.what() );
607         throwSuiteStatusException(kOfxStatFailed);
608 
609         return false;
610     }
611 #else
612 
613     return true;
614 #endif
615 }
616 
617 // sets the correct choice menu item from the inputSpace string value
618 void
inputCheck(double time)619 GenericOCIO::inputCheck(double time)
620 {
621 #ifdef OFX_IO_USING_OCIO
622 #ifdef OFX_OCIO_CHOICE
623     if (!_config || !_inputSpace) {
624         return;
625     }
626     if (!_choiceIsOk) {
627         // choice menu is dirty, only use the text entry
628 #ifdef OFX_OCIO_NOSECRET
629         _inputSpace->setEnabled(true);
630         _inputSpaceChoice->setEnabled(false);
631 #else
632         _inputSpace->setIsSecretAndDisabled(false);
633         _inputSpaceChoice->setIsSecretAndDisabled(true);
634 #endif
635 
636         return;
637     }
638     string inputSpaceName;
639     getInputColorspaceAtTime(time, inputSpaceName);
640     int inputSpaceIndex = _config->getIndexForColorSpace( inputSpaceName.c_str() );
641     if (inputSpaceIndex >= 0) {
642         int inputSpaceIndexOld;
643         _inputSpaceChoice->getValueAtTime(time, inputSpaceIndexOld);
644         // avoid an infinite loop on bad hosts (for examples those which don't set args.reason correctly)
645         if (inputSpaceIndexOld != inputSpaceIndex) {
646             _inputSpaceChoice->setValue(inputSpaceIndex);
647         }
648 #ifdef OFX_OCIO_NOSECRET
649         _inputSpace->setEnabled(false);
650         _inputSpaceChoice->setEnabled(true);
651 #else
652         _inputSpace->setIsSecretAndDisabled(true);
653         _inputSpaceChoice->setIsSecretAndDisabled(false);
654 #endif
655     } else {
656         // the input space name is not valid
657 #ifdef OFX_OCIO_NOSECRET
658         _inputSpace->setEnabled(true);
659         _inputSpaceChoice->setEnabled(false);
660 #else
661         _inputSpace->setIsSecretAndDisabled(false);
662         _inputSpaceChoice->setIsSecretAndDisabled(true);
663 #endif
664     }
665 #endif
666 #endif
667 }
668 
669 // sets the correct choice menu item from the outputSpace string value
670 void
outputCheck(double time)671 GenericOCIO::outputCheck(double time)
672 {
673 #ifdef OFX_IO_USING_OCIO
674 #ifdef OFX_OCIO_CHOICE
675     if (!_config || !_outputSpace) {
676         return;
677     }
678     if (!_choiceIsOk) {
679         // choice menu is dirty, only use the text entry
680 #ifdef OFX_OCIO_NOSECRET
681         _outputSpace->setEnabled(true);
682         _outputSpaceChoice->setEnabled(false);
683 #else
684         _outputSpace->setIsSecretAndDisabled(false);
685         _outputSpaceChoice->setIsSecretAndDisabled(true);
686 #endif
687 
688         return;
689     }
690     string outputSpaceName;
691     getOutputColorspaceAtTime(time, outputSpaceName);
692     int outputSpaceIndex = _config->getIndexForColorSpace( outputSpaceName.c_str() );
693     if (outputSpaceIndex >= 0) {
694         int outputSpaceIndexOld;
695         _outputSpaceChoice->getValueAtTime(time, outputSpaceIndexOld);
696         // avoid an infinite loop on bad hosts (for examples those which don't set args.reason correctly)
697         if (outputSpaceIndexOld != outputSpaceIndex) {
698             _outputSpaceChoice->setValue(outputSpaceIndex);
699         }
700 #ifdef OFX_OCIO_NOSECRET
701         _outputSpace->setEnabled(false);
702         _outputSpaceChoice->setEnabled(true);
703 #else
704         _outputSpace->setIsSecretAndDisabled(true);
705         _outputSpaceChoice->setIsSecretAndDisabled(false);
706 #endif
707     } else {
708         // the output space name is not valid
709 #ifdef OFX_OCIO_NOSECRET
710         _outputSpace->setEnabled(true);
711         _outputSpaceChoice->setEnabled(false);
712 #else
713         _outputSpace->setIsSecretAndDisabled(false);
714         _outputSpaceChoice->setIsSecretAndDisabled(true);
715 #endif
716     }
717 #endif
718 #endif
719 }
720 
721 void
apply(double time,const OfxRectI & renderWindow,Image * img)722 GenericOCIO::apply(double time,
723                    const OfxRectI& renderWindow,
724                    Image* img)
725 {
726     assert(_created);
727 #ifdef OFX_IO_USING_OCIO
728     BitDepthEnum bitDepth = img->getPixelDepth();
729     if (bitDepth != eBitDepthFloat) {
730         throw std::runtime_error("OCIO: invalid pixel depth (only float is supported)");
731     }
732 
733     apply( time, renderWindow, (float*)img->getPixelData(), img->getBounds(), img->getPixelComponents(), img->getPixelComponentCount(), img->getRowBytes() );
734 #endif
735 }
736 
737 #ifdef OFX_IO_USING_OCIO
738 OCIO::ConstProcessorRcPtr
getProcessor() const739 GenericOCIO::getProcessor() const
740 {
741     AutoMutex guard(_procMutex);
742 
743     return _proc;
744 };
745 void
setValues(const string & inputSpace,const string & outputSpace)746 GenericOCIO::setValues(const string& inputSpace,
747                        const string& outputSpace)
748 {
749     return setValues( _config->getCurrentContext(), inputSpace.c_str(), outputSpace.c_str() );
750 }
751 
752 void
setValues(const OCIO::ConstContextRcPtr & context,const string & inputSpace,const string & outputSpace)753 GenericOCIO::setValues(const OCIO::ConstContextRcPtr &context,
754                        const string& inputSpace,
755                        const string& outputSpace)
756 {
757     AutoMutex guard(_procMutex);
758 
759     if ( !_proc ||
760          ( context != _procContext) ||
761          ( inputSpace != _procInputSpace) ||
762          ( outputSpace != _procOutputSpace) ) {
763         _procContext = context;
764         _procInputSpace = inputSpace;
765         _procOutputSpace = outputSpace;
766         _proc = _config->getProcessor( context, inputSpace.c_str(), outputSpace.c_str() );
767     }
768 }
769 
770 void
multiThreadProcessImages(OfxRectI renderWindow)771 OCIOProcessor::multiThreadProcessImages(OfxRectI renderWindow)
772 {
773     assert(_dstBounds.x1 <= renderWindow.x1 && renderWindow.x1 <= renderWindow.x2 && renderWindow.x2 <= _dstBounds.x2);
774     assert(_dstBounds.y1 <= renderWindow.y1 && renderWindow.y1 <= renderWindow.y2 && renderWindow.y2 <= _dstBounds.y2);
775     // Ensure there are pixels to render otherwise OCIO::PackedImageDesc will throw an exception.
776     if ( (renderWindow.y2 <= renderWindow.y1) || (renderWindow.x2 <= renderWindow.x1) ) {
777         return;
778     }
779 #ifdef OFX_IO_USING_OCIO
780     if (!_proc) {
781         throw std::logic_error("OCIO configuration not loaded");
782     }
783     int numChannels;
784     int pixelBytes;
785     switch (_dstPixelComponents) {
786     case ePixelComponentRGBA:
787         numChannels = 4;
788         break;
789     case ePixelComponentRGB:
790         numChannels = 3;
791         break;
792     //case ePixelComponentAlpha: pixelBytes = 1; break;
793     default:
794         throwSuiteStatusException(kOfxStatErrFormat);
795 
796         return;
797     }
798 
799     pixelBytes = numChannels * sizeof(float);
800     size_t pixelDataOffset = (size_t)(renderWindow.y1 - _dstBounds.y1) * _dstRowBytes + (size_t)(renderWindow.x1 - _dstBounds.x1) * pixelBytes;
801     float *pix = (float *) ( ( (char *) _dstPixelData ) + pixelDataOffset ); // (char*)dstImg->getPixelAddress(renderWindow.x1, renderWindow.y1);
802     try {
803         if (_proc) {
804             OCIO::PackedImageDesc img(pix, renderWindow.x2 - renderWindow.x1, renderWindow.y2 - renderWindow.y1, numChannels, sizeof(float), pixelBytes, _dstRowBytes);
805             _proc->apply(img);
806         }
807     } catch (OCIO::Exception &e) {
808         _instance->setPersistentMessage( Message::eMessageError, "", string("OpenColorIO error: ") + e.what() );
809         throw std::runtime_error( string("OpenColorIO error: ") + e.what() );
810     }
811 #endif
812 }
813 
814 #endif // OFX_IO_USING_OCIO
815 
816 #ifdef OFX_IO_USING_OCIO
817 OCIO::ConstProcessorRcPtr
getOrCreateProcessor(double time)818 GenericOCIO::getOrCreateProcessor(double time)
819 {
820     if (!_config) {
821         return OCIO::ConstProcessorRcPtr();
822     }
823     string inputSpace;
824     getInputColorspaceAtTime(time, inputSpace);
825     string outputSpace;
826     getOutputColorspaceAtTime(time, outputSpace);
827     OCIO::ConstContextRcPtr context = getLocalContext(time);//_config->getCurrentContext();
828     setValues(context, inputSpace, outputSpace);
829 
830     return getProcessor();
831 }
832 
833 #endif // OFX_IO_USING_OCIO
834 
835 void
apply(double time,const OfxRectI & renderWindow,float * pixelData,const OfxRectI & bounds,PixelComponentEnum pixelComponents,int pixelComponentCount,int rowBytes)836 GenericOCIO::apply(double time,
837                    const OfxRectI& renderWindow,
838                    float *pixelData,
839                    const OfxRectI& bounds,
840                    PixelComponentEnum pixelComponents,
841                    int pixelComponentCount,
842                    int rowBytes)
843 {
844     assert(_created);
845 #ifdef OFX_IO_USING_OCIO
846 
847     if (!_created) {
848         return;
849     }
850 
851     if ( isIdentity(time) ) {
852         return;
853     }
854     // are we in the image bounds
855     if ( (renderWindow.x1 < bounds.x1) || (renderWindow.x1 >= bounds.x2) || (renderWindow.y1 < bounds.y1) || (renderWindow.y1 >= bounds.y2) ||
856          ( renderWindow.x2 <= bounds.x1) || ( renderWindow.x2 > bounds.x2) || ( renderWindow.y2 <= bounds.y1) || ( renderWindow.y2 > bounds.y2) ) {
857         _parent->setPersistentMessage(Message::eMessageError, "", "OCIO: render window outside of image bounds");
858         throwSuiteStatusException(kOfxStatFailed);
859     }
860     if ( (pixelComponents != ePixelComponentRGBA) && (pixelComponents != ePixelComponentRGB) ) {
861         _parent->setPersistentMessage(Message::eMessageError, "", "OCIO: invalid components (only RGB and RGBA are supported)");
862         throwSuiteStatusException(kOfxStatFailed);
863     }
864 
865     OCIO::ConstProcessorRcPtr proc = getOrCreateProcessor(time);
866     if (!proc) {
867         _parent->setPersistentMessage( Message::eMessageError, "", "Cannot create OCIO processor" );
868         throwSuiteStatusException(kOfxStatFailed);
869 
870         return;
871     }
872 
873     OCIOProcessor processor(*_parent);
874     // set the images
875     processor.setDstImg(pixelData, bounds, pixelComponents, pixelComponentCount, eBitDepthFloat, rowBytes);
876 
877     processor.setProcessor(proc);
878 
879     // set the render window
880     processor.setRenderWindow(renderWindow);
881 
882     // Call the base class process member, this will call the derived templated process code
883     processor.process();
884 #endif
885 }
886 
887 void
changedParam(const InstanceChangedArgs & args,const string & paramName)888 GenericOCIO::changedParam(const InstanceChangedArgs &args,
889                           const string &paramName)
890 {
891     assert(_created);
892 #ifdef OFX_IO_USING_OCIO
893     if ( (paramName == kOCIOParamConfigFile) && (args.reason != eChangeTime) ) {
894         // must clear persistent message, or render() is not called by Nuke after an error
895         _parent->clearPersistentMessage();
896         // compute canonical inputSpace and outputSpace before changing the config,
897         // if different from inputSpace and outputSpace they must be set to the canonical value after changing ocio config
898         string inputSpace;
899         getInputColorspaceAtTime(args.time, inputSpace);
900         string inputSpaceCanonical = canonicalizeColorSpace(_config, inputSpace);
901         if (inputSpaceCanonical != inputSpace) {
902             _inputSpace->setValue(inputSpaceCanonical);
903         }
904         if (_outputSpace) {
905             string outputSpace;
906             getOutputColorspaceAtTime(args.time, outputSpace);
907             string outputSpaceCanonical = canonicalizeColorSpace(_config, outputSpace);
908             if (outputSpaceCanonical != outputSpace) {
909                 _outputSpace->setValue(outputSpaceCanonical);
910             }
911         }
912 
913         loadConfig(); // re-load the new OCIO config
914         //if inputspace or outputspace are not valid in the new config, reset them to "default"
915         if (_config) {
916             string inputSpaceName;
917             getInputColorspaceAtTime(args.time, inputSpaceName);
918             int inputSpaceIndex = _config->getIndexForColorSpace( inputSpaceName.c_str() );
919             if (inputSpaceIndex < 0) {
920                 OCIO::ConstColorSpaceRcPtr cs;
921                 if (!cs) {
922                     cs = _config->getColorSpace(OCIO::ROLE_DEFAULT);
923                 }
924                 if (!cs) {
925                     // no default colorspace, fallback to the first one
926                     cs = _config->getColorSpace( _config->getColorSpaceNameByIndex(0) );
927                 }
928                 inputSpaceName = cs ? cs->getName() : OCIO::ROLE_DEFAULT;
929                 _inputSpace->setValue(inputSpaceName);
930             }
931         }
932         inputCheck(args.time);
933         if (_config && _outputSpace) {
934             string outputSpaceName;
935             getOutputColorspaceAtTime(args.time, outputSpaceName);
936             int outputSpaceIndex = _config->getIndexForColorSpace( outputSpaceName.c_str() );
937             if (outputSpaceIndex < 0) {
938                 OCIO::ConstColorSpaceRcPtr cs;
939                 if (!cs) {
940                     cs = _config->getColorSpace(OCIO::ROLE_DEFAULT);
941                 }
942                 if (!cs) {
943                     // no default colorspace, fallback to the first one
944                     cs = _config->getColorSpace( _config->getColorSpaceNameByIndex(0) );
945                 }
946                 outputSpaceName = cs ? cs->getName() : OCIO::ROLE_DEFAULT;
947                 _outputSpace->setValue(OCIO::ROLE_DEFAULT);
948             }
949         }
950         outputCheck(args.time);
951 
952         if ( !_config && (args.reason == eChangeUserEdit) ) {
953             string filename;
954             _ocioConfigFile->getValue(filename);
955             _parent->sendMessage(Message::eMessageError, "", string("Cannot load OCIO config file \"") + filename + '"');
956         }
957     } else if ( (paramName == kOCIOHelpButton) || (paramName == kOCIOHelpLooksButton) || (paramName == kOCIOHelpDisplaysButton) ) {
958         string msg = "OpenColorIO Help\n"
959                      "The OCIO configuration file can be set using the \"OCIO\" environment variable, which should contain the full path to the .ocio file.\n"
960                      "OpenColorIO version (compiled with / running with): " OCIO_VERSION "/";
961         msg += OCIO::GetVersion();
962         msg += '\n';
963         if (_config) {
964             string configdesc = _config->getDescription();
965             configdesc = whitespacify( trim(configdesc) );
966             if (configdesc.size() > 0) {
967                 msg += "\nThis OCIO configuration is ";
968                 msg += configdesc;
969                 msg += '\n';
970             }
971             msg += '\n';
972             if (paramName == kOCIOHelpLooksButton) {
973                 msg += (_config->getNumLooks() <= 0 ? "No look available in this OCIO configuration.\n" : "Available looks in this OCIO Configuration (applied in the given colorspace):\n");
974                 for (int i = 0; i < _config->getNumLooks(); ++i) {
975                     const char* lkname = _config->getLookNameByIndex(i);
976                     OCIO::ConstLookRcPtr lk = _config->getLook(lkname);
977                     msg += "- ";
978                     msg += lkname;
979                     string lkspace = lk->getProcessSpace();
980                     msg += " (" + lkspace + ")\n";
981                 }
982                 msg += '\n';
983             }
984             if (paramName == kOCIOHelpDisplaysButton) {
985                 if (_config->getNumDisplays() <= 0) {
986                     msg += "No display available in this OCIO configuration.\n";
987                 } else {
988                     msg += "Available displays and views in this OCIO Configuration:\n";
989                     string defaultdisplay = _config->getDefaultDisplay();
990                     for (int i = 0; i < _config->getNumDisplays(); ++i) {
991                         const char* display = _config->getDisplay(i);
992                         msg += "- ";
993                         msg += display;
994                         if (display == defaultdisplay) {
995                             msg += " (default)";
996                         }
997                         int numViews = _config->getNumViews(display);
998                         if (numViews <= 0) {
999                             msg += ", no view available.\n";
1000                         } else {
1001                             msg += ", views: ";
1002                             string defaultview = _config->getDefaultView(display);
1003                             for (int j = 0; j < numViews; ++j) {
1004                                 const char* view = _config->getView(display, j);
1005                                 msg += view;
1006                                 if (view == defaultview) {
1007                                     msg += " (default)";
1008                                 }
1009                                 if (j < numViews - 1) {
1010                                     msg += ", ";
1011                                 }
1012                             }
1013                             msg += '\n';
1014                         }
1015                     }
1016                 }
1017                 msg += '\n';
1018             }
1019             msg += "Available colorspaces in this OCIO Configuration:\n";
1020             int defaultcs = _config->getIndexForColorSpace(OCIO::ROLE_DEFAULT);
1021             int referencecs = _config->getIndexForColorSpace(OCIO::ROLE_REFERENCE);
1022             int datacs = _config->getIndexForColorSpace(OCIO::ROLE_DATA);
1023             int colorpickingcs = _config->getIndexForColorSpace(OCIO::ROLE_COLOR_PICKING);
1024             int scenelinearcs = _config->getIndexForColorSpace(OCIO::ROLE_SCENE_LINEAR);
1025             int compositinglogcs = _config->getIndexForColorSpace(OCIO::ROLE_COMPOSITING_LOG);
1026             int colortimingcs = _config->getIndexForColorSpace(OCIO::ROLE_COLOR_TIMING);
1027             int texturepaintcs = _config->getIndexForColorSpace(OCIO::ROLE_TEXTURE_PAINT);
1028             int mattepaintcs = _config->getIndexForColorSpace(OCIO::ROLE_MATTE_PAINT);
1029 
1030             for (int i = 0; i < _config->getNumColorSpaces(); ++i) {
1031                 const char* csname = _config->getColorSpaceNameByIndex(i);;
1032                 OCIO::ConstColorSpaceRcPtr cs = _config->getColorSpace(csname);
1033                 msg += "- ";
1034                 msg += csname;
1035                 bool first = true;
1036                 //int roles = 0;
1037                 if (i == defaultcs) {
1038                     msg += first ? " (" : ", ";
1039                     msg += OCIO::ROLE_DEFAULT;
1040                     first = false;
1041                     //++roles;
1042                 }
1043                 if (i == referencecs) {
1044                     msg += first ? " (" : ", ";
1045                     msg += OCIO::ROLE_REFERENCE;
1046                     first = false;
1047                     //++roles;
1048                 }
1049                 if (i == datacs) {
1050                     msg += first ? " (" : ", ";
1051                     msg += OCIO::ROLE_DATA;
1052                     first = false;
1053                     //++roles;
1054                 }
1055                 if (i == colorpickingcs) {
1056                     msg += first ? " (" : ", ";
1057                     msg += OCIO::ROLE_COLOR_PICKING;
1058                     first = false;
1059                     //++roles;
1060                 }
1061                 if (i == scenelinearcs) {
1062                     msg += first ? " (" : ", ";
1063                     msg += OCIO::ROLE_SCENE_LINEAR;
1064                     first = false;
1065                     //++roles;
1066                 }
1067                 if (i == compositinglogcs) {
1068                     msg += first ? " (" : ", ";
1069                     msg += OCIO::ROLE_COMPOSITING_LOG;
1070                     first = false;
1071                     //++roles;
1072                 }
1073                 if (i == colortimingcs) {
1074                     msg += first ? " (" : ", ";
1075                     msg += OCIO::ROLE_COLOR_TIMING;
1076                     first = false;
1077                     //++roles;
1078                 }
1079                 if (i == texturepaintcs) {
1080                     msg += first ? " (" : ", ";
1081                     msg += OCIO::ROLE_TEXTURE_PAINT;
1082                     first = false;
1083                     //++roles;
1084                 }
1085                 if (i == mattepaintcs) {
1086                     msg += first ? " (" : ", ";
1087                     msg += OCIO::ROLE_MATTE_PAINT;
1088                     first = false;
1089                     //++roles;
1090                 }
1091                 if (!first /*&& roles > 0*/) {
1092                     msg += ')';
1093                 }
1094                 string csdesc = cs ? cs->getDescription() : "(no colorspace)";
1095                 csdesc = whitespacify( trim(csdesc) );
1096                 if ( !csdesc.empty() ) {
1097                     msg += ": ";
1098                     msg += csdesc;
1099                     msg += '\n';
1100                 } else {
1101                     msg += '\n';
1102                 }
1103             }
1104         }
1105         _parent->sendMessage(Message::eMessageMessage, "", msg);
1106     } else if (!_config) {
1107         // the other parameters assume there is a valid config
1108         return;
1109     } else if (paramName == kOCIOParamInputSpace) {
1110         assert(_inputSpace);
1111         if (args.reason == eChangeUserEdit) {
1112             // if the inputspace doesn't correspond to a valid one, reset to default.
1113             // first, canonicalize.
1114             string inputSpace;
1115             getInputColorspaceAtTime(args.time, inputSpace);
1116             string inputSpaceCanonical = canonicalizeColorSpace(_config, inputSpace);
1117             if (inputSpaceCanonical != inputSpace) {
1118                 _inputSpace->setValue(inputSpaceCanonical);
1119                 inputSpace = inputSpaceCanonical;
1120             }
1121             int inputSpaceIndex = _config->getIndexForColorSpace( inputSpace.c_str() );
1122             if (inputSpaceIndex < 0) {
1123                 if (args.reason == eChangeUserEdit) {
1124                     _parent->sendMessage(Message::eMessageWarning, "", string("Unknown OCIO colorspace \"") + inputSpace + "\"");
1125                 }
1126                 OCIO::ConstColorSpaceRcPtr cs;
1127                 if (!cs) {
1128                     cs = _config->getColorSpace(OCIO::ROLE_DEFAULT);
1129                 }
1130                 if (!cs) {
1131                     // no default colorspace, fallback to the first one
1132                     cs = _config->getColorSpace( _config->getColorSpaceNameByIndex(0) );
1133                 }
1134                 inputSpace = cs ? cs->getName() : OCIO::ROLE_DEFAULT;
1135                 _inputSpace->setValue(inputSpace);
1136                 inputSpaceIndex = _config->getIndexForColorSpace( inputSpace.c_str() );
1137                 assert(inputSpaceIndex >= 0);
1138             }
1139         }
1140         inputCheck(args.time);
1141     }
1142 #ifdef OFX_OCIO_CHOICE
1143     else if ( (paramName == kOCIOParamInputSpaceChoice) && (args.reason == eChangeUserEdit) ) {
1144         assert(_inputSpace);
1145         int inputSpaceIndex;
1146         _inputSpaceChoice->getValueAtTime(args.time, inputSpaceIndex);
1147         string inputSpaceOld;
1148         getInputColorspaceAtTime(args.time, inputSpaceOld);
1149         string inputSpace = canonicalizeColorSpace( _config, _config->getColorSpaceNameByIndex(inputSpaceIndex) );
1150         // avoid an infinite loop on bad hosts (for examples those which don't set args.reason correctly)
1151         if (inputSpace != inputSpaceOld) {
1152             _inputSpace->setValue(inputSpace);
1153         }
1154     }
1155 #endif
1156     else if (paramName == kOCIOParamOutputSpace) {
1157         assert(_outputSpace);
1158         if (args.reason == eChangeUserEdit) {
1159             // if the outputspace doesn't correspond to a valid one, reset to default.
1160             // first, canonicalize.
1161             string outputSpace;
1162             getOutputColorspaceAtTime(args.time, outputSpace);
1163             string outputSpaceCanonical = canonicalizeColorSpace(_config, outputSpace);
1164             if (outputSpaceCanonical != outputSpace) {
1165                 _outputSpace->setValue(outputSpaceCanonical);
1166                 outputSpace = outputSpaceCanonical;
1167             }
1168             int outputSpaceIndex = _config->getIndexForColorSpace( outputSpace.c_str() );
1169             if (outputSpaceIndex < 0) {
1170                 if (args.reason == eChangeUserEdit) {
1171                     _parent->sendMessage(Message::eMessageWarning, "", string("Unknown OCIO colorspace \"") + outputSpace + "\"");
1172                 }
1173                 OCIO::ConstColorSpaceRcPtr cs;
1174                 if (!cs) {
1175                     cs = _config->getColorSpace(OCIO::ROLE_DEFAULT);
1176                 }
1177                 if (!cs) {
1178                     // no default colorspace, fallback to the first one
1179                     cs = _config->getColorSpace( _config->getColorSpaceNameByIndex(0) );
1180                 }
1181                 outputSpace = cs ? cs->getName() : OCIO::ROLE_DEFAULT;
1182                 _outputSpace->setValue(outputSpace);
1183                 outputSpaceIndex = _config->getIndexForColorSpace( outputSpace.c_str() );
1184                 assert(outputSpaceIndex >= 0);
1185             }
1186         }
1187         outputCheck(args.time);
1188     }
1189 #ifdef OFX_OCIO_CHOICE
1190     else if ( (paramName == kOCIOParamOutputSpaceChoice) && (args.reason == eChangeUserEdit) ) {
1191         assert(_outputSpace);
1192         int outputSpaceIndex;
1193         _outputSpaceChoice->getValueAtTime(args.time, outputSpaceIndex);
1194         string outputSpaceOld;
1195         getOutputColorspaceAtTime(args.time, outputSpaceOld);
1196         string outputSpace = canonicalizeColorSpace( _config, _config->getColorSpaceNameByIndex(outputSpaceIndex) );
1197         // avoid an infinite loop on bad hosts (for examples those which don't set args.reason correctly)
1198         if (outputSpace != outputSpaceOld) {
1199             _outputSpace->setValue(outputSpace);
1200         }
1201     }
1202 #endif // OFX_OCIO_CHOICE
1203 
1204 
1205 #endif // ifdef OFX_IO_USING_OCIO
1206 } // GenericOCIO::changedParam
1207 
1208 #ifdef OFX_IO_USING_OCIO
1209 void
getInputColorspaceDefault(string & v) const1210 GenericOCIO::getInputColorspaceDefault(string &v) const
1211 {
1212     assert(_inputSpace);
1213     _inputSpace->getDefault(v);
1214 }
1215 
1216 void
getInputColorspace(string & v) const1217 GenericOCIO::getInputColorspace(string &v) const
1218 {
1219     assert(_inputSpace);
1220     _inputSpace->getValue(v);
1221 }
1222 
1223 void
getInputColorspaceAtTime(double time,string & v) const1224 GenericOCIO::getInputColorspaceAtTime(double time,
1225                                       string &v) const
1226 {
1227     assert(_inputSpace);
1228     _inputSpace->getValueAtTime(time, v);
1229 }
1230 
1231 void
getOutputColorspaceDefault(string & v) const1232 GenericOCIO::getOutputColorspaceDefault(string &v) const
1233 {
1234     assert(_outputSpace);
1235     _outputSpace->getDefault(v);
1236 }
1237 
1238 void
getOutputColorspace(string & v) const1239 GenericOCIO::getOutputColorspace(string &v) const
1240 {
1241     assert(_outputSpace);
1242     _outputSpace->getValue(v);
1243 }
1244 
1245 void
getOutputColorspaceAtTime(double time,string & v) const1246 GenericOCIO::getOutputColorspaceAtTime(double time,
1247                                        string &v) const
1248 {
1249     assert(_outputSpace);
1250     _outputSpace->getValueAtTime(time, v);
1251 }
1252 
1253 #endif
1254 
1255 
1256 bool
hasColorspace(const char * name) const1257 GenericOCIO::hasColorspace(const char* name) const
1258 {
1259 #ifdef OFX_IO_USING_OCIO
1260 
1261     return _config && (bool)_config->getColorSpace(name);
1262 #else
1263 
1264     return false;
1265 #endif
1266 }
1267 
1268 void
setInputColorspace(const char * name)1269 GenericOCIO::setInputColorspace(const char* name)
1270 {
1271 #ifdef OFX_IO_USING_OCIO
1272     assert(_inputSpace);
1273     _inputSpace->setValue(name);
1274 #endif
1275 }
1276 
1277 void
setOutputColorspace(const char * name)1278 GenericOCIO::setOutputColorspace(const char* name)
1279 {
1280 #ifdef OFX_IO_USING_OCIO
1281     assert(_outputSpace);
1282     _outputSpace->setValue(name);
1283 #endif
1284 }
1285 
1286 void
purgeCaches()1287 GenericOCIO::purgeCaches()
1288 {
1289 #ifdef OFX_IO_USING_OCIO
1290     OCIO::ClearAllCaches();
1291 #endif
1292 }
1293 
1294 void
describeInContextInput(ImageEffectDescriptor & desc,ContextEnum,PageParamDescriptor * page,const char * inputSpaceNameDefault,const char * inputSpaceLabel)1295 GenericOCIO::describeInContextInput(ImageEffectDescriptor &desc,
1296                                     ContextEnum /*context*/,
1297                                     PageParamDescriptor *page,
1298                                     const char* inputSpaceNameDefault,
1299                                     const char* inputSpaceLabel)
1300 {
1301 #ifdef OFX_IO_USING_OCIO
1302     gHostIsNatron = (getImageEffectHostDescription()->isNatron);
1303 
1304     char* file = std::getenv("OCIO");
1305     OCIO::ConstConfigRcPtr config;
1306     if (file != NULL) {
1307         //Add choices
1308         try {
1309             config = OCIO::Config::CreateFromFile(file);
1310             gWasOCIOEnvVarFound = true;
1311         } catch (OCIO::Exception &e) {
1312         }
1313     }
1314     string inputSpaceName, outputSpaceName;
1315     if (config) {
1316         inputSpaceName = canonicalizeColorSpace( config, colorSpaceName(config, inputSpaceNameDefault) );
1317     }
1318 
1319     ////////// OCIO config file
1320     {
1321         StringParamDescriptor* param = desc.defineStringParam(kOCIOParamConfigFile);
1322         param->setLabelAndHint(kOCIOParamConfigFileLabel);
1323         param->setStringType(eStringTypeFilePath);
1324         param->setFilePathExists(true);
1325         param->setAnimates(false);
1326         desc.addClipPreferencesSlaveParam(*param);
1327         // the OCIO config can only be set in a portable fashion using the environment variable.
1328         // Nuke, for example, doesn't support changing the entries in a ChoiceParam outside of describeInContext.
1329         // disable it, and set the default from the env variable.
1330         assert( getImageEffectHostDescription() );
1331         if (file == NULL) {
1332             param->setDefault("WARNING: Open an OCIO config file, or set the OCIO environnement variable");
1333         } else if (config) {
1334             param->setDefault(file);
1335         } else {
1336             string s("ERROR: Invalid OCIO configuration '");
1337             s += file;
1338             s += '\'';
1339             param->setDefault(s);
1340         }
1341         if (page) {
1342             page->addChild(*param);
1343         }
1344     }
1345 
1346     ///////////Input Color-space
1347     {
1348         StringParamDescriptor* param = desc.defineStringParam(kOCIOParamInputSpace);
1349         param->setLabelAndHint(inputSpaceLabel, kOCIOParamInputSpaceHint);
1350         param->setAnimates(true);
1351         if (config) {
1352             param->setDefault(inputSpaceName);
1353         } else {
1354             //param->setEnabled(false); // done in constructor
1355         }
1356         if (page) {
1357             page->addChild(*param);
1358         }
1359     }
1360 
1361 #ifdef OFX_OCIO_CHOICE
1362     {
1363         ChoiceParamDescriptor* param = desc.defineChoiceParam(kOCIOParamInputSpaceChoice);
1364         param->setLabelAndHint(inputSpaceLabel, kOCIOParamInputSpaceHint);
1365         param->setCascading(getImageEffectHostDescription()->supportsCascadingChoices);
1366         if (config) {
1367             buildChoiceMenu(config, param, getImageEffectHostDescription()->supportsCascadingChoices, inputSpaceName);
1368         } else {
1369             //param->setEnabled(false); // done in the plugin constructor
1370             //param->setIsSecret(true); // done in the plugin constructor
1371         }
1372         param->setAnimates(true);
1373         param->setEvaluateOnChange(false); // evaluate only when the StringParam is changed
1374         param->setIsPersistent(false); // don't save/serialize
1375         if (page) {
1376             page->addChild(*param);
1377         }
1378     }
1379 #endif
1380 #endif // ifdef OFX_IO_USING_OCIO
1381 } // GenericOCIO::describeInContextInput
1382 
1383 void
describeInContextOutput(ImageEffectDescriptor & desc,ContextEnum,PageParamDescriptor * page,const char * outputSpaceNameDefault,const char * outputSpaceLabel)1384 GenericOCIO::describeInContextOutput(ImageEffectDescriptor &desc,
1385                                      ContextEnum /*context*/,
1386                                      PageParamDescriptor *page,
1387                                      const char* outputSpaceNameDefault,
1388                                      const char* outputSpaceLabel)
1389 {
1390 #ifdef OFX_IO_USING_OCIO
1391     gHostIsNatron = (getImageEffectHostDescription()->isNatron);
1392 
1393     char* file = std::getenv("OCIO");
1394     OCIO::ConstConfigRcPtr config;
1395     if (file != NULL) {
1396         //Add choices
1397         try {
1398             config = OCIO::Config::CreateFromFile(file);
1399             gWasOCIOEnvVarFound = true;
1400         } catch (OCIO::Exception &e) {
1401         }
1402     }
1403     string outputSpaceName;
1404     if (config) {
1405         outputSpaceName = canonicalizeColorSpace( config, colorSpaceName(config, outputSpaceNameDefault) );
1406     }
1407 
1408     ///////////Output Color-space
1409     {
1410         StringParamDescriptor* param = desc.defineStringParam(kOCIOParamOutputSpace);
1411         param->setLabelAndHint(outputSpaceLabel, kOCIOParamOutputSpaceHint);
1412         param->setAnimates(true);
1413         if (config) {
1414             param->setDefault(outputSpaceName);
1415         } else {
1416             //param->setEnabled(false); // done in constructor
1417         }
1418         if (page) {
1419             page->addChild(*param);
1420         }
1421     }
1422 
1423 #ifdef OFX_OCIO_CHOICE
1424     {
1425         ChoiceParamDescriptor* param = desc.defineChoiceParam(kOCIOParamOutputSpaceChoice);
1426         param->setLabelAndHint(outputSpaceLabel, kOCIOParamOutputSpaceHint);
1427         param->setCascading(getImageEffectHostDescription()->supportsCascadingChoices);
1428         if (config) {
1429             buildChoiceMenu(config, param, getImageEffectHostDescription()->supportsCascadingChoices, outputSpaceName);
1430         } else {
1431             //param->setEnabled(false); // done in the plugin constructor
1432             //param->setIsSecret(true); // done in the plugin constructor
1433         }
1434         param->setAnimates(true);
1435         param->setEvaluateOnChange(false); // evaluate only when the StringParam is changed
1436         param->setIsPersistent(false); // don't save/serialize
1437         if (page) {
1438             page->addChild(*param);
1439         }
1440     }
1441 #endif
1442 #endif // ifdef OFX_IO_USING_OCIO
1443 } // GenericOCIO::describeInContextOutput
1444 
1445 void
describeInContextContext(ImageEffectDescriptor & desc,ContextEnum,PageParamDescriptor * page)1446 GenericOCIO::describeInContextContext(ImageEffectDescriptor &desc,
1447                                       ContextEnum /*context*/,
1448                                       PageParamDescriptor *page)
1449 {
1450 #ifdef OFX_IO_USING_OCIO
1451     GroupParamDescriptor* group = desc.defineGroupParam(kOCIOParamContext);
1452     group->setLabelAndHint(kOCIOParamContextLabel, kOCIOParamContextHint);
1453     group->setOpen(false);
1454 
1455     {
1456         StringParamDescriptor* param = desc.defineStringParam(kOCIOParamContextKey1);
1457         param->setHint(kOCIOParamContextHint);
1458         param->setAnimates(true);
1459         param->setParent(*group);
1460         param->setLayoutHint(eLayoutHintNoNewLine, 1);
1461         if (page) {
1462             page->addChild(*param);
1463         }
1464     }
1465     {
1466         StringParamDescriptor* param = desc.defineStringParam(kOCIOParamContextValue1);
1467         param->setHint(kOCIOParamContextHint);
1468         param->setAnimates(true);
1469         param->setParent(*group);
1470         if (page) {
1471             page->addChild(*param);
1472         }
1473     }
1474     {
1475         StringParamDescriptor* param = desc.defineStringParam(kOCIOParamContextKey2);
1476         param->setHint(kOCIOParamContextHint);
1477         param->setAnimates(true);
1478         param->setParent(*group);
1479         param->setLayoutHint(eLayoutHintNoNewLine, 1);
1480         if (page) {
1481             page->addChild(*param);
1482         }
1483     }
1484     {
1485         StringParamDescriptor* param = desc.defineStringParam(kOCIOParamContextValue2);
1486         param->setHint(kOCIOParamContextHint);
1487         param->setAnimates(true);
1488         param->setParent(*group);
1489         if (page) {
1490             page->addChild(*param);
1491         }
1492     }
1493     {
1494         StringParamDescriptor* param = desc.defineStringParam(kOCIOParamContextKey3);
1495         param->setHint(kOCIOParamContextHint);
1496         param->setAnimates(true);
1497         param->setParent(*group);
1498         param->setLayoutHint(eLayoutHintNoNewLine, 1);
1499         if (page) {
1500             page->addChild(*param);
1501         }
1502     }
1503     {
1504         StringParamDescriptor* param = desc.defineStringParam(kOCIOParamContextValue3);
1505         param->setHint(kOCIOParamContextHint);
1506         param->setAnimates(true);
1507         param->setParent(*group);
1508         if (page) {
1509             page->addChild(*param);
1510         }
1511     }
1512     {
1513         StringParamDescriptor* param = desc.defineStringParam(kOCIOParamContextKey4);
1514         param->setHint(kOCIOParamContextHint);
1515         param->setAnimates(true);
1516         param->setParent(*group);
1517         param->setLayoutHint(eLayoutHintNoNewLine, 1);
1518         if (page) {
1519             page->addChild(*param);
1520         }
1521     }
1522     {
1523         StringParamDescriptor* param = desc.defineStringParam(kOCIOParamContextValue4);
1524         param->setHint(kOCIOParamContextHint);
1525         param->setAnimates(true);
1526         param->setParent(*group);
1527         if (page) {
1528             page->addChild(*param);
1529         }
1530     }
1531     if (page) {
1532         page->addChild(*group);
1533     }
1534 #endif // ifdef OFX_IO_USING_OCIO
1535 } // GenericOCIO::describeInContextContext
1536 
1537 NAMESPACE_OFX_IO_EXIT
1538 NAMESPACE_OFX_EXIT
1539