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