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