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