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  * OCIOLookTransform plugin.
21  * Apply a "look".
22  */
23 
24 #ifdef OFX_IO_USING_OCIO
25 
26 //#include <iostream>
27 #include <memory>
28 #ifdef DEBUG
29 #include <cstdio> // printf
30 #endif
31 
32 #include <GenericOCIO.h>
33 
34 #include <ofxsProcessing.H>
35 #include <ofxsCopier.h>
36 #include "ofxsCoords.h"
37 #include <ofxsMacros.h>
38 #include <ofxNatron.h>
39 
40 #include "IOUtility.h"
41 
42 using namespace OFX;
43 using namespace IO;
44 
45 OFXS_NAMESPACE_ANONYMOUS_ENTER
46 
47 using std::string;
48 
49 #define kPluginName "OCIOLookTransformOFX"
50 #define kPluginGrouping "Color/OCIO"
51 #define kPluginDescription \
52     "OpenColorIO LookTransform\n\n" \
53     "A 'look' is a named color transform, intended to modify the look of an " \
54     "image in a 'creative' manner (as opposed to a colorspace definion which " \
55     "tends to be technically/mathematically defined).\n\n" \
56     "Examples of looks may be a neutral grade, to be applied to film scans " \
57     "prior to VFX work, or a per-shot DI grade decided on by the director, " \
58     "to be applied just before the viewing transform.\n\n" \
59     "OCIOLooks must be predefined in the OpenColorIO configuration before usage, " \
60     "and often reference per-shot/sequence LUTs/CCs.\n\n" \
61     "See the \'Look Combination\' parameter for further syntax details.\n\n" \
62     "See opencolorio.org for look configuration customization examples."
63 
64 #define kPluginIdentifier "fr.inria.openfx.OCIOLookTransform"
65 #define kPluginVersionMajor 1 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
66 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
67 
68 #define kSupportsTiles 1
69 #define kSupportsMultiResolution 1
70 #define kSupportsRenderScale 1
71 #define kRenderThreadSafety eRenderFullySafe
72 
73 #define kParamLookChoice "lookChoice"
74 #define kParamLookChoiceLabel "Look"
75 #define kParamLookChoiceHint "Look to apply (if \"Single Look\" is checked) or append to the Look Combination (when the \"Append\" button is pressed)."
76 
77 #define kParamLookAppend "append"
78 #define kParamLookAppendLabel "Append Look to Combination"
79 #define kParamLookAppendHint "Append the selected Look to the Look Combination"
80 
81 #define kParamSingleLook "singleLook"
82 #define kParamSingleLookLabel "Single Look"
83 #define kParamSingleLookHint "When checked, only the selected Look is applied. When not checked, the Look Combination is applied."
84 
85 #define kParamLookCombination "lookCombination"
86 #define kParamLookCombinationLabel "Look Combination"
87 #define kParamLookCombinationHint \
88     "Specify the look(s) to apply.\n" \
89     "This may be empty, the name of a single look, or a combination of looks using the 'look syntax'.\n" \
90     "If it is empty, no look is applied.\n" \
91     "Look Syntax:\n" \
92     "Multiple looks are combined with commas: 'firstlook, secondlook'\n" \
93     "Direction is specified with +/- prefixes: '+firstlook, -secondlook'\n" \
94     "Missing look 'fallbacks' specified with |: 'firstlook, -secondlook | -secondlook'"
95 
96 #define kParamDirection "direction"
97 #define kParamDirectionLabel "Direction"
98 #define kParamDirectionHint "Transform direction."
99 #define kParamDirectionOptionForward "Forward", "", "forward"
100 #define kParamDirectionOptionInverse "Inverse", "", "inverse"
101 
102 // OCIO's GPU render is not accurate enough.
103 // see https://github.com/imageworks/OpenColorIO/issues/394
104 // and https://github.com/imageworks/OpenColorIO/issues/456
105 #if defined(OFX_SUPPORTS_OPENGLRENDER)
106 #define kParamEnableGPU "enableGPU"
107 #define kParamEnableGPULabel "Enable GPU Render"
108 #define kParamEnableGPUHint \
109     "Enable GPU-based OpenGL render.\n" \
110     "Note that GPU render is not as accurate as CPU render, so this should be enabled with care.\n" \
111     "If the checkbox is checked but is not enabled (i.e. it cannot be unchecked), GPU render can not be enabled or disabled from the plugin and is probably part of the host options.\n" \
112     "If the checkbox is not checked and is not enabled (i.e. it cannot be checked), GPU render is not available on this host."
113 #endif
114 
115 namespace OCIO = OCIO_NAMESPACE;
116 
117 static bool gHostIsNatron   = false;
118 
119 // ChoiceParamType may be ChoiceParamDescriptor or ChoiceParam
120 template <typename ChoiceParamType>
121 static void
buildLookChoiceMenu(OCIO::ConstConfigRcPtr config,ChoiceParamType * choice)122 buildLookChoiceMenu(OCIO::ConstConfigRcPtr config,
123                     ChoiceParamType* choice)
124 {
125     choice->resetOptions();
126     if (!config) {
127         return;
128     }
129     for (int i = 0; i < config->getNumLooks(); ++i) {
130         choice->appendOption( config->getLookNameByIndex(i) );
131     }
132 }
133 
134 class OCIOLookTransformPlugin
135     : public ImageEffect
136 {
137 public:
138 
139     OCIOLookTransformPlugin(OfxImageEffectHandle handle);
140 
141     virtual ~OCIOLookTransformPlugin();
142 
143 private:
144     /* Override the render */
145     virtual void render(const RenderArguments &args) OVERRIDE FINAL;
146 
147     /* override is identity */
148     virtual bool isIdentity(const IsIdentityArguments &args, Clip * &identityClip, double &identityTime, int& view, std::string& plane) OVERRIDE FINAL;
149 
150     /* override changedParam */
151     virtual void changedParam(const InstanceChangedArgs &args, const string &paramName) OVERRIDE FINAL;
152 
153     /* override changed clip */
154     virtual void changedClip(const InstanceChangedArgs &args, const string &clipName) OVERRIDE FINAL;
155 
156     // override the rod call
157     //virtual bool getRegionOfDefinition(const RegionOfDefinitionArguments &args, OfxRectD &rod) OVERRIDE FINAL;
158 
159     // override the roi call
160     //virtual void getRegionsOfInterest(const RegionsOfInterestArguments &args, RegionOfInterestSetter &rois) OVERRIDE FINAL;
161 
162 #if defined(OFX_SUPPORTS_OPENGLRENDER)
163     /* The purpose of this action is to allow a plugin to set up any data it may need
164        to do OpenGL rendering in an instance. */
165     virtual void* contextAttached(bool createContextData) OVERRIDE FINAL;
166     /* The purpose of this action is to allow a plugin to deallocate any resource
167        allocated in \ref ::kOfxActionOpenGLContextAttached just before the host
168        decouples a plugin from an OpenGL context. */
169     virtual void contextDetached(void* contextData) OVERRIDE FINAL;
170 
171     void renderGPU(const RenderArguments &args);
172 #endif
173 
174     OCIO::ConstProcessorRcPtr getProcessor(OfxTime time, bool singleLook, const string& lookCombination);
175 
copyPixelData(bool unpremult,bool premult,bool maskmix,double time,const OfxRectI & renderWindow,const Image * srcImg,Image * dstImg)176     void copyPixelData(bool unpremult,
177                        bool premult,
178                        bool maskmix,
179                        double time,
180                        const OfxRectI &renderWindow,
181                        const Image* srcImg,
182                        Image* dstImg)
183     {
184         const void* srcPixelData;
185         OfxRectI srcBounds;
186         PixelComponentEnum srcPixelComponents;
187         BitDepthEnum srcBitDepth;
188         int srcRowBytes;
189 
190         getImageData(srcImg, &srcPixelData, &srcBounds, &srcPixelComponents, &srcBitDepth, &srcRowBytes);
191         int srcPixelComponentCount = srcImg->getPixelComponentCount();
192         void* dstPixelData;
193         OfxRectI dstBounds;
194         PixelComponentEnum dstPixelComponents;
195         BitDepthEnum dstBitDepth;
196         int dstRowBytes;
197         getImageData(dstImg, &dstPixelData, &dstBounds, &dstPixelComponents, &dstBitDepth, &dstRowBytes);
198         int dstPixelComponentCount = dstImg->getPixelComponentCount();
199         copyPixelData(unpremult,
200                       premult,
201                       maskmix,
202                       time,
203                       renderWindow,
204                       srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcBitDepth, srcRowBytes,
205                       dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
206     }
207 
copyPixelData(bool unpremult,bool premult,bool maskmix,double time,const OfxRectI & renderWindow,const void * srcPixelData,const OfxRectI & srcBounds,PixelComponentEnum srcPixelComponents,int srcPixelComponentCount,BitDepthEnum srcBitDepth,int srcRowBytes,Image * dstImg)208     void copyPixelData(bool unpremult,
209                        bool premult,
210                        bool maskmix,
211                        double time,
212                        const OfxRectI &renderWindow,
213                        const void *srcPixelData,
214                        const OfxRectI& srcBounds,
215                        PixelComponentEnum srcPixelComponents,
216                        int srcPixelComponentCount,
217                        BitDepthEnum srcBitDepth,
218                        int srcRowBytes,
219                        Image* dstImg)
220     {
221         void* dstPixelData;
222         OfxRectI dstBounds;
223         PixelComponentEnum dstPixelComponents;
224         BitDepthEnum dstBitDepth;
225         int dstRowBytes;
226 
227         getImageData(dstImg, &dstPixelData, &dstBounds, &dstPixelComponents, &dstBitDepth, &dstRowBytes);
228         int dstPixelComponentCount = dstImg->getPixelComponentCount();
229         copyPixelData(unpremult,
230                       premult,
231                       maskmix,
232                       time,
233                       renderWindow,
234                       srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcBitDepth, srcRowBytes,
235                       dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
236     }
237 
copyPixelData(bool unpremult,bool premult,bool maskmix,double time,const OfxRectI & renderWindow,const Image * srcImg,void * dstPixelData,const OfxRectI & dstBounds,PixelComponentEnum dstPixelComponents,int dstPixelComponentCount,BitDepthEnum dstBitDepth,int dstRowBytes)238     void copyPixelData(bool unpremult,
239                        bool premult,
240                        bool maskmix,
241                        double time,
242                        const OfxRectI &renderWindow,
243                        const Image* srcImg,
244                        void *dstPixelData,
245                        const OfxRectI& dstBounds,
246                        PixelComponentEnum dstPixelComponents,
247                        int dstPixelComponentCount,
248                        BitDepthEnum dstBitDepth,
249                        int dstRowBytes)
250     {
251         const void* srcPixelData;
252         OfxRectI srcBounds;
253         PixelComponentEnum srcPixelComponents;
254         BitDepthEnum srcBitDepth;
255         int srcRowBytes;
256 
257         getImageData(srcImg, &srcPixelData, &srcBounds, &srcPixelComponents, &srcBitDepth, &srcRowBytes);
258         int srcPixelComponentCount = srcImg->getPixelComponentCount();
259         copyPixelData(unpremult,
260                       premult,
261                       maskmix,
262                       time,
263                       renderWindow,
264                       srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcBitDepth, srcRowBytes,
265                       dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
266     }
267 
268     void copyPixelData(bool unpremult,
269                        bool premult,
270                        bool maskmix,
271                        double time,
272                        const OfxRectI &renderWindow,
273                        const void *srcPixelData,
274                        const OfxRectI& srcBounds,
275                        PixelComponentEnum srcPixelComponents,
276                        int srcPixelComponentCount,
277                        BitDepthEnum srcPixelDepth,
278                        int srcRowBytes,
279                        void *dstPixelData,
280                        const OfxRectI& dstBounds,
281                        PixelComponentEnum dstPixelComponents,
282                        int dstPixelComponentCount,
283                        BitDepthEnum dstBitDepth,
284                        int dstRowBytes);
285 
286     void setupAndCopy(PixelProcessorFilterBase & processor,
287                       double time,
288                       const OfxRectI &renderWindow,
289                       const void *srcPixelData,
290                       const OfxRectI& srcBounds,
291                       PixelComponentEnum srcPixelComponents,
292                       int srcPixelComponentCount,
293                       BitDepthEnum srcPixelDepth,
294                       int srcRowBytes,
295                       void *dstPixelData,
296                       const OfxRectI& dstBounds,
297                       PixelComponentEnum dstPixelComponents,
298                       int dstPixelComponentCount,
299                       BitDepthEnum dstPixelDepth,
300                       int dstRowBytes);
301 
302     void apply(double time, const OfxRectI& renderWindow, float *pixelData, const OfxRectI& bounds, PixelComponentEnum pixelComponents, int pixelComponentCount, int rowBytes);
303 
304     // do not need to delete these, the ImageEffect is managing them for us
305     Clip *_dstClip;
306     Clip *_srcClip;
307     Clip *_maskClip;
308     ChoiceParam* _lookChoice;
309     PushButtonParam* _lookAppend;
310     BooleanParam* _singleLook;
311     StringParam* _lookCombination;
312     ChoiceParam *_direction;
313     BooleanParam* _premult;
314     ChoiceParam* _premultChannel;
315     DoubleParam* _mix;
316     BooleanParam* _maskApply;
317     BooleanParam* _maskInvert;
318     BooleanParam* _enableGPU;
319 
320     auto_ptr<GenericOCIO> _ocio;
321 
322     GenericOCIO::Mutex _procMutex;
323     OCIO::ConstProcessorRcPtr _proc;
324     string _procLook;
325     string _procInputSpace;
326     string _procOutputSpace;
327     int _procDirection;
328 
329 #if defined(OFX_SUPPORTS_OPENGLRENDER)
330     OCIOOpenGLContextData* _openGLContextData; // (OpenGL-only) - the single openGL context, in case the host does not support kNatronOfxImageEffectPropOpenGLContextData
331 #endif
332 };
333 
OCIOLookTransformPlugin(OfxImageEffectHandle handle)334 OCIOLookTransformPlugin::OCIOLookTransformPlugin(OfxImageEffectHandle handle)
335     : ImageEffect(handle)
336     , _dstClip(NULL)
337     , _srcClip(NULL)
338     , _maskClip(NULL)
339     , _lookChoice(NULL)
340     , _lookAppend(NULL)
341     , _singleLook(NULL)
342     , _lookCombination(NULL)
343     , _direction(NULL)
344     , _premult(NULL)
345     , _premultChannel(NULL)
346     , _mix(NULL)
347     , _maskApply(NULL)
348     , _maskInvert(NULL)
349     , _enableGPU(NULL)
350     , _ocio( new GenericOCIO(this) )
351     , _procDirection(-1)
352 #if defined(OFX_SUPPORTS_OPENGLRENDER)
353     , _openGLContextData(NULL)
354 #endif
355 {
356     _dstClip = fetchClip(kOfxImageEffectOutputClipName);
357     assert( _dstClip && (!_dstClip->isConnected() || _dstClip->getPixelComponents() == ePixelComponentRGBA ||
358                          _dstClip->getPixelComponents() == ePixelComponentRGB) );
359     _srcClip = getContext() == eContextGenerator ? NULL : fetchClip(kOfxImageEffectSimpleSourceClipName);
360     assert( (!_srcClip && getContext() == eContextGenerator) ||
361             ( _srcClip && (!_srcClip->isConnected() || _srcClip->getPixelComponents() == ePixelComponentRGBA ||
362                            _srcClip->getPixelComponents() == ePixelComponentRGB) ) );
363     _maskClip = fetchClip(getContext() == eContextPaint ? "Brush" : "Mask");
364     assert(!_maskClip || !_maskClip->isConnected() || _maskClip->getPixelComponents() == ePixelComponentAlpha);
365     _lookChoice = fetchChoiceParam(kParamLookChoice);
366     _lookAppend = fetchPushButtonParam(kParamLookAppend);
367     _singleLook = fetchBooleanParam(kParamSingleLook);
368     _lookCombination = fetchStringParam(kParamLookCombination);
369     assert(_lookChoice && _lookAppend && _singleLook && _lookCombination);
370     _direction = fetchChoiceParam(kParamDirection);
371     assert(_direction);
372     _premult = fetchBooleanParam(kParamPremult);
373     _premultChannel = fetchChoiceParam(kParamPremultChannel);
374     assert(_premult && _premultChannel);
375     _mix = fetchDoubleParam(kParamMix);
376     _maskApply = paramExists(kParamMaskApply) ? fetchBooleanParam(kParamMaskApply) : 0;
377     _maskInvert = fetchBooleanParam(kParamMaskInvert);
378     assert(_mix && _maskInvert);
379 
380 #if defined(OFX_SUPPORTS_OPENGLRENDER)
381     _enableGPU = fetchBooleanParam(kParamEnableGPU);
382     assert(_enableGPU);
383     const ImageEffectHostDescription &gHostDescription = *getImageEffectHostDescription();
384     if (!gHostDescription.supportsOpenGLRender) {
385         _enableGPU->setEnabled(false);
386     }
387     // GPU rendering is wrong when (un)premult is checked
388     setSupportsOpenGLRender( _enableGPU->getValue() && !_premult->getValue() );
389 #endif
390 
391     bool singleLook = _singleLook->getValue();
392     _lookChoice->setEvaluateOnChange(singleLook);
393     _lookCombination->setEnabled(!singleLook);
394     _lookCombination->setEvaluateOnChange(!singleLook);
395 
396     OCIO::ConstConfigRcPtr config = _ocio->getConfig();
397     if (!config) {
398         // secret should not be set on the descriptor, unless the parameter should *always* be secret
399         _lookChoice->setIsSecretAndDisabled(true);
400         _lookAppend->setIsSecretAndDisabled(true);
401         _singleLook->setIsSecretAndDisabled(true);
402     } else if ( !_ocio->configIsDefault() ) {
403         if (gHostIsNatron) {
404             // the choice menu can only be modified in Natron
405             // Natron supports changing the entries in a choiceparam
406             // Nuke (at least up to 8.0v3) does not
407             OCIO::ConstConfigRcPtr config = _ocio->getConfig();
408             buildLookChoiceMenu(config, _lookChoice);
409         } else {
410             _lookChoice->setIsSecretAndDisabled(true);
411             _lookAppend->setIsSecretAndDisabled(true);
412             _singleLook->setValue(true);
413             _singleLook->setIsSecretAndDisabled(true);
414         }
415     }
416 }
417 
~OCIOLookTransformPlugin()418 OCIOLookTransformPlugin::~OCIOLookTransformPlugin()
419 {
420 }
421 
422 /* set up and run a copy processor */
423 void
setupAndCopy(PixelProcessorFilterBase & processor,double time,const OfxRectI & renderWindow,const void * srcPixelData,const OfxRectI & srcBounds,PixelComponentEnum srcPixelComponents,int srcPixelComponentCount,BitDepthEnum srcPixelDepth,int srcRowBytes,void * dstPixelData,const OfxRectI & dstBounds,PixelComponentEnum dstPixelComponents,int dstPixelComponentCount,BitDepthEnum dstPixelDepth,int dstRowBytes)424 OCIOLookTransformPlugin::setupAndCopy(PixelProcessorFilterBase & processor,
425                                       double time,
426                                       const OfxRectI &renderWindow,
427                                       const void *srcPixelData,
428                                       const OfxRectI& srcBounds,
429                                       PixelComponentEnum srcPixelComponents,
430                                       int srcPixelComponentCount,
431                                       BitDepthEnum srcPixelDepth,
432                                       int srcRowBytes,
433                                       void *dstPixelData,
434                                       const OfxRectI& dstBounds,
435                                       PixelComponentEnum dstPixelComponents,
436                                       int dstPixelComponentCount,
437                                       BitDepthEnum dstPixelDepth,
438                                       int dstRowBytes)
439 {
440     assert(srcPixelData && dstPixelData);
441 
442     // make sure bit depths are sane
443     if ( (srcPixelDepth != dstPixelDepth) || (srcPixelComponents != dstPixelComponents) ) {
444         throwSuiteStatusException(kOfxStatErrFormat);
445 
446         return;
447     }
448 
449     auto_ptr<const Image> orig( ( _srcClip && _srcClip->isConnected() ) ?
450                                      _srcClip->fetchImage(time) : 0 );
451 
452     bool doMasking = ( ( !_maskApply || _maskApply->getValueAtTime(time) ) && _maskClip && _maskClip->isConnected() );
453     auto_ptr<const Image> mask(doMasking ? _maskClip->fetchImage(time) : 0);
454     if (doMasking) {
455         bool maskInvert = _maskInvert->getValueAtTime(time);
456         processor.doMasking(true);
457         processor.setMaskImg(mask.get(), maskInvert);
458     }
459 
460     if ( !orig.get() ) {
461         throwSuiteStatusException(kOfxStatFailed);
462 
463         return;
464     }
465     // set the images
466     assert(orig.get() && dstPixelData && srcPixelData);
467     processor.setOrigImg( orig.get() );
468     processor.setDstImg(dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstPixelDepth, dstRowBytes);
469     processor.setSrcImg(srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcPixelDepth, srcRowBytes, 0);
470 
471     // set the render window
472     processor.setRenderWindow(renderWindow);
473 
474     bool premult = _premult->getValueAtTime(time);
475     int premultChannel = _premultChannel->getValueAtTime(time);
476     double mix = _mix->getValueAtTime(time);
477     processor.setPremultMaskMix(premult, premultChannel, mix);
478 
479     // Call the base class process member, this will call the derived templated process code
480     processor.process();
481 }
482 
483 void
copyPixelData(bool unpremult,bool premult,bool maskmix,double time,const OfxRectI & renderWindow,const void * srcPixelData,const OfxRectI & srcBounds,PixelComponentEnum srcPixelComponents,int srcPixelComponentCount,BitDepthEnum srcPixelDepth,int srcRowBytes,void * dstPixelData,const OfxRectI & dstBounds,PixelComponentEnum dstPixelComponents,int dstPixelComponentCount,BitDepthEnum dstBitDepth,int dstRowBytes)484 OCIOLookTransformPlugin::copyPixelData(bool unpremult,
485                                        bool premult,
486                                        bool maskmix,
487                                        double time,
488                                        const OfxRectI& renderWindow,
489                                        const void *srcPixelData,
490                                        const OfxRectI& srcBounds,
491                                        PixelComponentEnum srcPixelComponents,
492                                        int srcPixelComponentCount,
493                                        BitDepthEnum srcPixelDepth,
494                                        int srcRowBytes,
495                                        void *dstPixelData,
496                                        const OfxRectI& dstBounds,
497                                        PixelComponentEnum dstPixelComponents,
498                                        int dstPixelComponentCount,
499                                        BitDepthEnum dstBitDepth,
500                                        int dstRowBytes)
501 {
502     assert(srcPixelData && dstPixelData);
503     // do the rendering
504     if ( (dstBitDepth != eBitDepthFloat) || ( (dstPixelComponents != ePixelComponentRGBA) && (dstPixelComponents != ePixelComponentRGB) && (dstPixelComponents != ePixelComponentAlpha) ) ) {
505         throwSuiteStatusException(kOfxStatErrFormat);
506 
507         return;
508     }
509     if (!unpremult && !premult && !maskmix) {
510         copyPixels(*this, renderWindow, srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcPixelDepth, srcRowBytes, dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
511     } else if (unpremult && !premult && !maskmix) {
512         if (dstPixelComponents == ePixelComponentRGBA) {
513             PixelCopierUnPremult<float, 4, 1, float, 4, 1> fred(*this);
514             setupAndCopy(fred, time, renderWindow, srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcPixelDepth, srcRowBytes, dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
515         } else if (dstPixelComponents == ePixelComponentRGB) {
516             PixelCopierUnPremult<float, 3, 1, float, 3, 1> fred(*this);
517             setupAndCopy(fred, time, renderWindow, srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcPixelDepth, srcRowBytes, dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
518         }  else if (dstPixelComponents == ePixelComponentAlpha) {
519             PixelCopierUnPremult<float, 1, 1, float, 1, 1> fred(*this);
520             setupAndCopy(fred, time, renderWindow, srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcPixelDepth, srcRowBytes, dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
521         } // switch
522     } else if (!unpremult && !premult && maskmix) {
523         if (dstPixelComponents == ePixelComponentRGBA) {
524             PixelCopierMaskMix<float, 4, 1, true> fred(*this);
525             setupAndCopy(fred, time, renderWindow, srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcPixelDepth, srcRowBytes, dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
526         } else if (dstPixelComponents == ePixelComponentRGB) {
527             PixelCopierMaskMix<float, 3, 1, true> fred(*this);
528             setupAndCopy(fred, time, renderWindow, srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcPixelDepth, srcRowBytes, dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
529         }  else if (dstPixelComponents == ePixelComponentAlpha) {
530             PixelCopierMaskMix<float, 1, 1, true> fred(*this);
531             setupAndCopy(fred, time, renderWindow, srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcPixelDepth, srcRowBytes, dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
532         } // switch
533     } else if (!unpremult && premult && maskmix) {
534         if (dstPixelComponents == ePixelComponentRGBA) {
535             PixelCopierPremultMaskMix<float, 4, 1, float, 4, 1> fred(*this);
536             setupAndCopy(fred, time, renderWindow, srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcPixelDepth, srcRowBytes, dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
537         } else if (dstPixelComponents == ePixelComponentRGB) {
538             PixelCopierPremultMaskMix<float, 3, 1, float, 3, 1> fred(*this);
539             setupAndCopy(fred, time, renderWindow, srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcPixelDepth, srcRowBytes, dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
540         }  else if (dstPixelComponents == ePixelComponentAlpha) {
541             PixelCopierPremultMaskMix<float, 1, 1, float, 1, 1> fred(*this);
542             setupAndCopy(fred, time, renderWindow, srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcPixelDepth, srcRowBytes, dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
543         } // switch
544     } else {
545         assert(false); // should never happen
546     }
547 }
548 
549 OCIO::ConstProcessorRcPtr
getProcessor(OfxTime time,bool singleLook,const string & lookCombination)550 OCIOLookTransformPlugin::getProcessor(OfxTime time,
551                                       bool singleLook,
552                                       const string& lookCombination)
553 {
554     OCIO::ConstConfigRcPtr config = _ocio->getConfig();
555     if (!config) {
556         setPersistentMessage(Message::eMessageError, "", "OCIO: no current config");
557         throwSuiteStatusException(kOfxStatFailed);
558 
559         return _proc;
560     }
561 
562     string inputSpace;
563     _ocio->getInputColorspaceAtTime(time, inputSpace);
564     string look;
565     if (singleLook) {
566         int lookChoice_i = _lookChoice->getValueAtTime(time);
567         look = config->getLookNameByIndex(lookChoice_i);
568     } else {
569         look = lookCombination;
570     }
571     int directioni = _direction->getValueAtTime(time);
572     string outputSpace;
573     _ocio->getOutputColorspaceAtTime(time, outputSpace);
574     try {
575         GenericOCIO::AutoMutex guard(_procMutex);
576         if ( !_proc ||
577              ( _procLook != look) ||
578              ( _procInputSpace != inputSpace) ||
579              ( _procOutputSpace != outputSpace) ||
580              ( _procDirection != directioni) ) {
581             OCIO::TransformDirection direction = OCIO::TRANSFORM_DIR_UNKNOWN;
582             OCIO::LookTransformRcPtr transform = OCIO::LookTransform::Create();
583             transform->setLooks( look.c_str() );
584 
585             if (directioni == 0) {
586                 transform->setSrc( inputSpace.c_str() );
587                 transform->setDst( outputSpace.c_str() );
588                 direction = OCIO::TRANSFORM_DIR_FORWARD;
589             } else {
590                 // The TRANSFORM_DIR_INVERSE applies an inverse for the end-to-end transform,
591                 // which would otherwise do dst->inv look -> src.
592                 // This is an unintuitive result for the artist (who would expect in, out to
593                 // remain unchanged), so we account for that here by flipping src/dst
594 
595                 transform->setSrc( outputSpace.c_str() );
596                 transform->setDst( inputSpace.c_str() );
597                 direction = OCIO::TRANSFORM_DIR_INVERSE;
598             }
599             _proc = config->getProcessor(transform, direction);
600         }
601 
602         return _proc;
603     } catch (const OCIO::Exception &e) {
604         setPersistentMessage( Message::eMessageError, "", e.what() );
605         throwSuiteStatusException(kOfxStatFailed);
606 
607         return _proc;
608     }
609 } // getProcessor
610 
611 void
apply(double time,const OfxRectI & renderWindow,float * pixelData,const OfxRectI & bounds,PixelComponentEnum pixelComponents,int pixelComponentCount,int rowBytes)612 OCIOLookTransformPlugin::apply(double time,
613                                const OfxRectI& renderWindow,
614                                float *pixelData,
615                                const OfxRectI& bounds,
616                                PixelComponentEnum pixelComponents,
617                                int pixelComponentCount,
618                                int rowBytes)
619 {
620     // are we in the image bounds
621     if ( (renderWindow.x1 < bounds.x1) || (renderWindow.x1 >= bounds.x2) || (renderWindow.y1 < bounds.y1) || (renderWindow.y1 >= bounds.y2) ||
622          ( renderWindow.x2 <= bounds.x1) || ( renderWindow.x2 > bounds.x2) || ( renderWindow.y2 <= bounds.y1) || ( renderWindow.y2 > bounds.y2) ) {
623         throw std::runtime_error("OCIO: render window outside of image bounds");
624     }
625     if ( (pixelComponents != ePixelComponentRGBA) && (pixelComponents != ePixelComponentRGB) ) {
626         throw std::runtime_error("OCIO: invalid components (only RGB and RGBA are supported)");
627     }
628 
629 
630     OCIOProcessor processor(*this);
631     bool singleLook = _singleLook->getValueAtTime(time);
632     string lookCombination;
633     _lookCombination->getValueAtTime(time, lookCombination);
634     if ( _ocio->isIdentity(time) && !singleLook && lookCombination.empty() ) {
635         return; // isIdentity
636     }
637 
638     processor.setProcessor( getProcessor(time, singleLook, lookCombination) );
639 
640     // set the images
641     processor.setDstImg(pixelData, bounds, pixelComponents, pixelComponentCount, eBitDepthFloat, rowBytes);
642 
643 
644     // set the render window
645     processor.setRenderWindow(renderWindow);
646 
647     // Call the base class process member, this will call the derived templated process code
648     processor.process();
649 }
650 
651 #if defined(OFX_SUPPORTS_OPENGLRENDER)
652 
653 /*
654  * Action called when an effect has just been attached to an OpenGL
655  * context.
656  *
657  * The purpose of this action is to allow a plugin to set up any data it may need
658  * to do OpenGL rendering in an instance. For example...
659  *  - allocate a lookup table on a GPU,
660  *  - create an openCL or CUDA context that is bound to the host's OpenGL
661  *    context so it can share buffers.
662  */
663 void*
contextAttached(bool createContextData)664 OCIOLookTransformPlugin::contextAttached(bool createContextData)
665 {
666 #ifdef DEBUG
667     if (getImageEffectHostDescription()->isNatron && !createContextData) {
668         std::printf("ERROR: Natron did not ask to create context data\n");
669     }
670 #endif
671     if (createContextData) {
672         return new OCIOOpenGLContextData;
673     } else {
674         if (_openGLContextData) {
675 #         ifdef DEBUG
676             std::printf("ERROR: contextAttached() called but context already attached\n");
677 #         endif
678             contextDetached(NULL);
679         }
680         _openGLContextData = new OCIOOpenGLContextData;
681     }
682 
683     return NULL;
684 }
685 
686 /*
687  * Action called when an effect is about to be detached from an
688  * OpenGL context
689  *
690  * The purpose of this action is to allow a plugin to deallocate any resource
691  * allocated in \ref ::kOfxActionOpenGLContextAttached just before the host
692  * decouples a plugin from an OpenGL context.
693  * The host must call this with the same OpenGL context active as it
694  * called with the corresponding ::kOfxActionOpenGLContextAttached.
695  */
696 void
contextDetached(void * contextData)697 OCIOLookTransformPlugin::contextDetached(void* contextData)
698 {
699     if (contextData) {
700         OCIOOpenGLContextData* myData = (OCIOOpenGLContextData*)contextData;
701         delete myData;
702     } else {
703         if (!_openGLContextData) {
704 #         ifdef DEBUG
705             std::printf("ERROR: contextDetached() called but no context attached\n");
706 #         endif
707         }
708         delete _openGLContextData;
709         _openGLContextData = NULL;
710     }
711 }
712 
713 void
renderGPU(const RenderArguments & args)714 OCIOLookTransformPlugin::renderGPU(const RenderArguments &args)
715 {
716     auto_ptr<Texture> srcImg( _srcClip->loadTexture(args.time) );
717     if ( !srcImg.get() ) {
718         throwSuiteStatusException(kOfxStatFailed);
719 
720         return;
721     }
722 
723     if ( (srcImg->getRenderScale().x != args.renderScale.x) ||
724          ( srcImg->getRenderScale().y != args.renderScale.y) ||
725          ( srcImg->getField() != args.fieldToRender) ) {
726         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
727         throwSuiteStatusException(kOfxStatFailed);
728 
729         return;
730     }
731 
732     auto_ptr<Texture> dstImg( _dstClip->loadTexture(args.time) );
733     if ( !dstImg.get() ) {
734         throwSuiteStatusException(kOfxStatFailed);
735 
736         return;
737     }
738     if ( (dstImg->getRenderScale().x != args.renderScale.x) ||
739          ( dstImg->getRenderScale().y != args.renderScale.y) ||
740          ( dstImg->getField() != args.fieldToRender) ) {
741         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
742         throwSuiteStatusException(kOfxStatFailed);
743 
744         return;
745     }
746 
747     BitDepthEnum srcBitDepth = srcImg->getPixelDepth();
748     PixelComponentEnum srcComponents = srcImg->getPixelComponents();
749     BitDepthEnum dstBitDepth = dstImg->getPixelDepth();
750     if ( (dstBitDepth != eBitDepthFloat) || (dstBitDepth != srcBitDepth) ) {
751         throwSuiteStatusException(kOfxStatErrFormat);
752 
753         return;
754     }
755 
756     PixelComponentEnum dstComponents  = dstImg->getPixelComponents();
757     if ( ( (dstComponents != ePixelComponentRGBA) && (dstComponents != ePixelComponentRGB) && (dstComponents != ePixelComponentAlpha) ) ||
758          ( dstComponents != srcComponents) ) {
759         throwSuiteStatusException(kOfxStatErrFormat);
760 
761         return;
762     }
763 
764     // are we in the image bounds
765     OfxRectI dstBounds = dstImg->getBounds();
766     if ( (args.renderWindow.x1 < dstBounds.x1) || (args.renderWindow.x1 >= dstBounds.x2) || (args.renderWindow.y1 < dstBounds.y1) || (args.renderWindow.y1 >= dstBounds.y2) ||
767          ( args.renderWindow.x2 <= dstBounds.x1) || ( args.renderWindow.x2 > dstBounds.x2) || ( args.renderWindow.y2 <= dstBounds.y1) || ( args.renderWindow.y2 > dstBounds.y2) ) {
768         throwSuiteStatusException(kOfxStatErrValue);
769 
770         return;
771         //throw std::runtime_error("render window outside of image bounds");
772     }
773 
774     OCIOOpenGLContextData* contextData = NULL;
775     if (getImageEffectHostDescription()->isNatron && !args.openGLContextData) {
776 #     ifdef DEBUG
777         std::printf("ERROR: Natron did not provide the contextData pointer to the OpenGL render func.\n");
778 #     endif
779     }
780     if (args.openGLContextData) {
781         // host provided kNatronOfxImageEffectPropOpenGLContextData,
782         // which was returned by kOfxActionOpenGLContextAttached
783         contextData = (OCIOOpenGLContextData*)args.openGLContextData;
784     } else {
785         if (!_openGLContextData) {
786             // Sony Catalyst Edit never calls kOfxActionOpenGLContextAttached
787 #         ifdef DEBUG
788             std::printf( ("ERROR: OpenGL render() called without calling contextAttached() first. Calling it now.\n") );
789 #         endif
790             contextAttached(false);
791             assert(_openGLContextData);
792         }
793         contextData = _openGLContextData;
794     }
795     if (!contextData) {
796         throwSuiteStatusException(kOfxStatFailed);
797     }
798 
799     bool singleLook = _singleLook->getValueAtTime(args.time);
800     string lookCombination;
801     _lookCombination->getValueAtTime(args.time, lookCombination);
802     if ( _ocio->isIdentity(args.time) && !singleLook && lookCombination.empty() ) {
803         return; // isIdentity
804     }
805 
806     OCIO::ConstProcessorRcPtr proc = getProcessor(args.time, singleLook, lookCombination);
807     assert(proc);
808 
809     GenericOCIO::applyGL(srcImg.get(), proc, &contextData->procLut3D, &contextData->procLut3DID, &contextData->procShaderProgramID, &contextData->procFragmentShaderID, &contextData->procLut3DCacheID, &contextData->procShaderCacheID);
810 } // renderGPU
811 
812 #endif // defined(OFX_SUPPORTS_OPENGLRENDER)
813 
814 
815 /* Override the render */
816 void
render(const RenderArguments & args)817 OCIOLookTransformPlugin::render(const RenderArguments &args)
818 {
819     if (!_srcClip) {
820         throwSuiteStatusException(kOfxStatFailed);
821 
822         return;
823     }
824     if (!_dstClip) {
825         throwSuiteStatusException(kOfxStatFailed);
826 
827         return;
828     }
829     assert(_srcClip && _dstClip);
830 
831 #if defined(OFX_SUPPORTS_OPENGLRENDER)
832     if (args.openGLEnabled) {
833         renderGPU(args);
834 
835         return;
836     }
837 #endif
838 
839     if (!_srcClip) {
840         throwSuiteStatusException(kOfxStatFailed);
841 
842         return;
843     }
844     assert(_srcClip);
845     auto_ptr<const Image> srcImg( _srcClip->fetchImage(args.time) );
846     if ( !srcImg.get() ) {
847         throwSuiteStatusException(kOfxStatFailed);
848 
849         return;
850     }
851     if ( (srcImg->getRenderScale().x != args.renderScale.x) ||
852          ( srcImg->getRenderScale().y != args.renderScale.y) ||
853          ( srcImg->getField() != args.fieldToRender) ) {
854         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
855         throwSuiteStatusException(kOfxStatFailed);
856 
857         return;
858     }
859 
860     BitDepthEnum srcBitDepth = srcImg->getPixelDepth();
861     PixelComponentEnum srcComponents = srcImg->getPixelComponents();
862 
863     if (!_dstClip) {
864         throwSuiteStatusException(kOfxStatFailed);
865 
866         return;
867     }
868     assert(_dstClip);
869     auto_ptr<Image> dstImg( _dstClip->fetchImage(args.time) );
870     if ( !dstImg.get() ) {
871         throwSuiteStatusException(kOfxStatFailed);
872 
873         return;
874     }
875     if ( (dstImg->getRenderScale().x != args.renderScale.x) ||
876          ( dstImg->getRenderScale().y != args.renderScale.y) ||
877          ( dstImg->getField() != args.fieldToRender) ) {
878         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
879         throwSuiteStatusException(kOfxStatFailed);
880 
881         return;
882     }
883 
884     BitDepthEnum dstBitDepth = dstImg->getPixelDepth();
885     if ( (dstBitDepth != eBitDepthFloat) || (dstBitDepth != srcBitDepth) ) {
886         throwSuiteStatusException(kOfxStatErrFormat);
887 
888         return;
889     }
890 
891     PixelComponentEnum dstComponents  = dstImg->getPixelComponents();
892     if ( ( (dstComponents != ePixelComponentRGBA) && (dstComponents != ePixelComponentRGB) && (dstComponents != ePixelComponentAlpha) ) ||
893          ( dstComponents != srcComponents) ) {
894         throwSuiteStatusException(kOfxStatErrFormat);
895 
896         return;
897     }
898 
899     // are we in the image bounds
900     OfxRectI dstBounds = dstImg->getBounds();
901     if ( (args.renderWindow.x1 < dstBounds.x1) || (args.renderWindow.x1 >= dstBounds.x2) || (args.renderWindow.y1 < dstBounds.y1) || (args.renderWindow.y1 >= dstBounds.y2) ||
902          ( args.renderWindow.x2 <= dstBounds.x1) || ( args.renderWindow.x2 > dstBounds.x2) || ( args.renderWindow.y2 <= dstBounds.y1) || ( args.renderWindow.y2 > dstBounds.y2) ) {
903         throwSuiteStatusException(kOfxStatErrValue);
904 
905         return;
906         //throw std::runtime_error("render window outside of image bounds");
907     }
908 
909     const void* srcPixelData = NULL;
910     OfxRectI bounds;
911     PixelComponentEnum pixelComponents;
912     BitDepthEnum bitDepth;
913     int srcRowBytes;
914     getImageData(srcImg.get(), &srcPixelData, &bounds, &pixelComponents, &bitDepth, &srcRowBytes);
915     int pixelComponentCount = srcImg->getPixelComponentCount();
916 
917     // allocate temporary image
918     int pixelBytes = pixelComponentCount * getComponentBytes(srcBitDepth);
919     int tmpRowBytes = (args.renderWindow.x2 - args.renderWindow.x1) * pixelBytes;
920     size_t memSize = (args.renderWindow.y2 - args.renderWindow.y1) * tmpRowBytes;
921     ImageMemory mem(memSize, this);
922     float *tmpPixelData = (float*)mem.lock();
923     bool premult;
924     _premult->getValueAtTime(args.time, premult);
925 
926     // copy renderWindow to the temporary image
927     copyPixelData(premult, false, false, args.time, args.renderWindow, srcPixelData, bounds, pixelComponents, pixelComponentCount, bitDepth, srcRowBytes, tmpPixelData, args.renderWindow, pixelComponents, pixelComponentCount, bitDepth, tmpRowBytes);
928 
929     ///do the color-space conversion
930     apply(args.time, args.renderWindow, tmpPixelData, args.renderWindow, pixelComponents, pixelComponentCount, tmpRowBytes);
931 
932     // copy the color-converted window and apply masking
933     copyPixelData( false, premult, true, args.time, args.renderWindow, tmpPixelData, args.renderWindow, pixelComponents, pixelComponentCount, bitDepth, tmpRowBytes, dstImg.get() );
934 } // OCIOLookTransformPlugin::render
935 
936 bool
isIdentity(const IsIdentityArguments & args,Clip * & identityClip,double &,int &,std::string &)937 OCIOLookTransformPlugin::isIdentity(const IsIdentityArguments &args,
938                                     Clip * &identityClip,
939                                     double & /*identityTime*/
940                                     , int& /*view*/, std::string& /*plane*/)
941 {
942     if ( _ocio->isIdentity(args.time) ) {
943         bool singleLook;
944         _singleLook->getValueAtTime(args.time, singleLook);
945         if (!singleLook) {
946             string lookCombination;
947             _lookCombination->getValueAtTime(args.time, lookCombination);
948             if ( lookCombination.empty() ) {
949                 identityClip = _srcClip;
950 
951                 return true;
952             }
953         }
954     }
955 
956     double mix;
957     _mix->getValueAtTime(args.time, mix);
958 
959     if (mix == 0.) {
960         identityClip = _srcClip;
961 
962         return true;
963     }
964 
965     bool doMasking = ( ( !_maskApply || _maskApply->getValueAtTime(args.time) ) && _maskClip && _maskClip->isConnected() );
966     if (doMasking) {
967         bool maskInvert;
968         _maskInvert->getValueAtTime(args.time, maskInvert);
969         if (!maskInvert) {
970             OfxRectI maskRoD;
971             Coords::toPixelEnclosing(_maskClip->getRegionOfDefinition(args.time), args.renderScale, _maskClip->getPixelAspectRatio(), &maskRoD);
972             // effect is identity if the renderWindow doesn't intersect the mask RoD
973             if ( !Coords::rectIntersection<OfxRectI>(args.renderWindow, maskRoD, 0) ) {
974                 identityClip = _srcClip;
975 
976                 return true;
977             }
978         }
979     }
980 
981     return false;
982 }
983 
984 void
changedParam(const InstanceChangedArgs & args,const string & paramName)985 OCIOLookTransformPlugin::changedParam(const InstanceChangedArgs &args,
986                                       const string &paramName)
987 {
988     // must clear persistent message, or render() is not called by Nuke after an error
989     clearPersistentMessage();
990     if (paramName == kParamLookAppend) {
991         OCIO::ConstConfigRcPtr config = _ocio->getConfig();
992         string lookCombination;
993         int lookChoice;
994         _lookCombination->getValueAtTime(args.time, lookCombination);
995         _lookChoice->getValueAtTime(args.time, lookChoice);
996         string look = config->getLookNameByIndex(lookChoice);
997         if ( !look.empty() ) {
998             if ( lookCombination.empty() ) {
999                 lookCombination = look;
1000             } else {
1001                 lookCombination += ", ";
1002                 lookCombination += look;
1003             }
1004             _lookCombination->setValue(lookCombination);
1005         }
1006     } else if ( (paramName == kParamSingleLook) && (args.reason == eChangeUserEdit) ) {
1007         bool singleLook;
1008         _singleLook->getValueAtTime(args.time, singleLook);
1009         _lookChoice->setEvaluateOnChange(singleLook);
1010         _lookCombination->setEnabled(!singleLook);
1011         _lookCombination->setEvaluateOnChange(!singleLook);
1012 #if defined(OFX_SUPPORTS_OPENGLRENDER)
1013     } else if (paramName == kParamEnableGPU) {
1014         // GPU rendering is wrong when (un)premult is checked
1015         bool supportsGL = _enableGPU->getValueAtTime(args.time) && !_premult->getValueAtTime(args.time);
1016         setSupportsOpenGLRender(supportsGL);
1017         setSupportsTiles(!supportsGL);
1018 #endif
1019     } else {
1020         _ocio->changedParam(args, paramName);
1021     }
1022     // this must be done after handling by GenericOCIO (to make sure the new config is loaded)
1023     if ( (paramName == kOCIOParamConfigFile) && (args.reason == eChangeUserEdit) ) {
1024         if ( !_ocio->configIsDefault() ) {
1025             if (gHostIsNatron) {
1026                 // the choice menu can only be modified in Natron
1027                 // Natron supports changing the entries in a choiceparam
1028                 // Nuke (at least up to 8.0v3) does not
1029                 OCIO::ConstConfigRcPtr config = _ocio->getConfig();
1030                 buildLookChoiceMenu(config, _lookChoice);
1031             } else {
1032                 _lookChoice->setIsSecretAndDisabled(true);
1033                 _lookAppend->setIsSecretAndDisabled(true);
1034                 _singleLook->setValue(true);
1035                 _singleLook->setIsSecretAndDisabled(true);
1036             }
1037         }
1038     }
1039 } // OCIOLookTransformPlugin::changedParam
1040 
1041 void
changedClip(const InstanceChangedArgs & args,const string & clipName)1042 OCIOLookTransformPlugin::changedClip(const InstanceChangedArgs &args,
1043                                      const string &clipName)
1044 {
1045     if ( (clipName == kOfxImageEffectSimpleSourceClipName) && _srcClip && (args.reason == eChangeUserEdit) ) {
1046         if (_srcClip->getPixelComponents() != ePixelComponentRGBA) {
1047             _premult->setValue(false);
1048         } else {
1049             switch ( _srcClip->getPreMultiplication() ) {
1050             case eImageOpaque:
1051                 _premult->setValue(false);
1052                 break;
1053             case eImagePreMultiplied:
1054                 _premult->setValue(true);
1055                 break;
1056             case eImageUnPreMultiplied:
1057                 _premult->setValue(false);
1058                 break;
1059             }
1060         }
1061     }
1062 }
1063 
1064 mDeclarePluginFactory(OCIOLookTransformPluginFactory, {ofxsThreadSuiteCheck();}, {});
1065 
1066 /** @brief The basic describe function, passed a plugin descriptor */
1067 void
describe(ImageEffectDescriptor & desc)1068 OCIOLookTransformPluginFactory::describe(ImageEffectDescriptor &desc)
1069 {
1070     // basic labels
1071     desc.setLabel(kPluginName);
1072     desc.setPluginGrouping(kPluginGrouping);
1073     desc.setPluginDescription(kPluginDescription);
1074 
1075     // add the supported contexts
1076     desc.addSupportedContext(eContextGeneral);
1077     desc.addSupportedContext(eContextFilter);
1078     desc.addSupportedContext(eContextPaint);
1079 
1080     // add supported pixel depths
1081     desc.addSupportedBitDepth(eBitDepthFloat);
1082 
1083     desc.setSupportsTiles(kSupportsTiles);
1084     desc.setSupportsMultiResolution(kSupportsMultiResolution);
1085     desc.setRenderThreadSafety(kRenderThreadSafety);
1086 
1087 #ifdef OFX_SUPPORTS_OPENGLRENDER
1088     desc.setSupportsOpenGLRender(true);
1089 #endif
1090 }
1091 
1092 /** @brief The describe in context function, passed a plugin descriptor and a context */
1093 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)1094 OCIOLookTransformPluginFactory::describeInContext(ImageEffectDescriptor &desc,
1095                                                   ContextEnum context)
1096 {
1097     // Source clip only in the filter context
1098     // create the mandated source clip
1099     ClipDescriptor *srcClip = desc.defineClip(kOfxImageEffectSimpleSourceClipName);
1100 
1101     srcClip->addSupportedComponent(ePixelComponentRGBA);
1102     srcClip->addSupportedComponent(ePixelComponentRGB);
1103     srcClip->setTemporalClipAccess(false);
1104     srcClip->setSupportsTiles(kSupportsTiles);
1105     srcClip->setIsMask(false);
1106 
1107     // create the mandated output clip
1108     ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
1109     dstClip->addSupportedComponent(ePixelComponentRGBA);
1110     dstClip->addSupportedComponent(ePixelComponentRGB);
1111     dstClip->setSupportsTiles(kSupportsTiles);
1112 
1113     ClipDescriptor *maskClip = (context == eContextPaint) ? desc.defineClip("Brush") : desc.defineClip("Mask");
1114     maskClip->addSupportedComponent(ePixelComponentAlpha);
1115     maskClip->setTemporalClipAccess(false);
1116     if (context != eContextPaint) {
1117         maskClip->setOptional(true);
1118     }
1119     maskClip->setSupportsTiles(kSupportsTiles);
1120     maskClip->setIsMask(true);
1121 
1122     gHostIsNatron = (getImageEffectHostDescription()->isNatron);
1123 
1124     // make some pages and to things in
1125     PageParamDescriptor *page = desc.definePageParam("Controls");
1126     // insert OCIO parameters
1127     GenericOCIO::describeInContextInput(desc, context, page, OCIO::ROLE_REFERENCE);
1128     OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
1129     {
1130         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamSingleLook);
1131         param->setLabel(kParamSingleLookLabel);
1132         param->setHint(kParamSingleLookHint);
1133         if (config) {
1134             param->setDefault(true);
1135         } else {
1136             param->setDefault(false);
1137             //param->setEnabled(false); // done in constructor
1138         }
1139         if (page) {
1140             page->addChild(*param);
1141         }
1142     }
1143     {
1144         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamLookChoice);
1145         param->setLabel(kParamLookChoiceLabel);
1146         param->setHint(kParamLookChoiceHint);
1147         if (config) {
1148             buildLookChoiceMenu(config, param);
1149         } else {
1150             //param->setEnabled(false); // done in constructor
1151         }
1152         param->setAnimates(true);
1153         if (page) {
1154             page->addChild(*param);
1155         }
1156     }
1157     {
1158         PushButtonParamDescriptor* param = desc.definePushButtonParam(kParamLookAppend);
1159         param->setLabel(kParamLookAppendLabel);
1160         param->setHint(kParamLookAppendHint);
1161         if (!config) {
1162             //param->setEnabled(false); // done in constructor
1163         }
1164         if (page) {
1165             page->addChild(*param);
1166         }
1167     }
1168     {
1169         StringParamDescriptor* param = desc.defineStringParam(kParamLookCombination);
1170         param->setLabel(kParamLookCombinationLabel);
1171         param->setHint(kParamLookCombinationHint);
1172         if (page) {
1173             page->addChild(*param);
1174         }
1175     }
1176     {
1177         ChoiceParamDescriptor *param = desc.defineChoiceParam(kParamDirection);
1178         param->setLabel(kParamDirectionLabel);
1179         param->setHint(kParamDirectionHint);
1180         param->appendOption(kParamDirectionOptionForward);
1181         param->appendOption(kParamDirectionOptionInverse);
1182         param->setDefault(0);
1183         if (page) {
1184             page->addChild(*param);
1185         }
1186     }
1187     GenericOCIO::describeInContextOutput(desc, context, page, OCIO::ROLE_REFERENCE);
1188     GenericOCIO::describeInContextContext(desc, context, page);
1189     {
1190         PushButtonParamDescriptor* param = desc.definePushButtonParam(kOCIOHelpLooksButton);
1191         param->setLabel(kOCIOHelpButtonLabel);
1192         param->setHint(kOCIOHelpButtonHint);
1193         if (page) {
1194             page->addChild(*param);
1195         }
1196     }
1197 
1198 
1199 #if defined(OFX_SUPPORTS_OPENGLRENDER)
1200     {
1201         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamEnableGPU);
1202         param->setLabel(kParamEnableGPULabel);
1203         param->setHint(kParamEnableGPUHint);
1204         const ImageEffectHostDescription &gHostDescription = *getImageEffectHostDescription();
1205         // Resolve advertises OpenGL support in its host description, but never calls render with OpenGL enabled
1206         if ( gHostDescription.supportsOpenGLRender && (gHostDescription.hostName != "DaVinciResolveLite") ) {
1207             // OCIO's GPU render is not accurate enough.
1208             // see https://github.com/imageworks/OpenColorIO/issues/394
1209             param->setDefault(/*true*/false);
1210             if (gHostDescription.APIVersionMajor * 100 + gHostDescription.APIVersionMinor < 104) {
1211                 // Switching OpenGL render from the plugin was introduced in OFX 1.4
1212                 param->setEnabled(false);
1213             }
1214         } else {
1215             param->setDefault(false);
1216             param->setEnabled(false);
1217         }
1218 
1219         if (page) {
1220             page->addChild(*param);
1221         }
1222     }
1223 #endif
1224 
1225     ofxsPremultDescribeParams(desc, page);
1226     ofxsMaskMixDescribeParams(desc, page);
1227 } // OCIOLookTransformPluginFactory::describeInContext
1228 
1229 /** @brief The create instance function, the plugin must return an object derived from the \ref ImageEffect class */
1230 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)1231 OCIOLookTransformPluginFactory::createInstance(OfxImageEffectHandle handle,
1232                                                ContextEnum /*context*/)
1233 {
1234     return new OCIOLookTransformPlugin(handle);
1235 }
1236 
1237 static OCIOLookTransformPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
1238 mRegisterPluginFactoryInstance(p)
1239 
1240 OFXS_NAMESPACE_ANONYMOUS_EXIT
1241 
1242 #endif // OFX_IO_USING_OCIO
1243 
1244