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