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 ¶mName) 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 ¶mName)
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