1 /* ***** BEGIN LICENSE BLOCK *****
2 * This file is part of openfx-misc <https://github.com/devernay/openfx-misc>,
3 * Copyright (C) 2013-2018 INRIA
4 *
5 * openfx-misc 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-misc 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-misc. If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
17 * ***** END LICENSE BLOCK ***** */
18
19 //
20 // CImgFilter.h
21 //
22 // A base class to simplify the creation of CImg plugins that have one image as input and an optional mask.
23 //
24
25 #ifndef Misc_CImgFilter_h
26 #define Misc_CImgFilter_h
27
28 #define PLUGIN_PACK_GPL2 // include GPL2 plugins by default
29
30 #include <cassert>
31 #include <memory>
32 #include <algorithm> // max
33
34 #include "ofxsImageEffect.h"
35 #include "ofxsMacros.h"
36 #include "ofxsPixelProcessor.h"
37 #include "ofxsCopier.h"
38 #include "ofxsCoords.h"
39 #ifdef OFX_EXTENSIONS_NATRON
40 #include "ofxNatron.h"
41 #endif
42 #include "ofxsThreadSuite.h"
43
44 #ifdef thread_local
45 # define HAVE_THREAD_LOCAL
46 #else
47 # if __STDC_VERSION__ >= 201112 && !defined __STDC_NO_THREADS__
48 # define thread_local _Thread_local
49 # define HAVE_THREAD_LOCAL
50 # elif defined _WIN32 && ( \
51 defined _MSC_VER || \
52 defined __ICL || \
53 defined __DMC__ || \
54 defined __BORLANDC__ )
55 // For DLLs that are loaded dynamically after the process has started (delay load, COM objects,
56 // explicit LoadLibrary, etc) __declspec(thread) does not work on Windows XP, 2003 Server and
57 // earlier OSes, but does work on Vista and 2008 Server.
58 // http://msdn.microsoft.com/en-us/library/9w1sdazb%28v=vs.80%29.aspx#1
59 // Unfortunately, OFX plugins are loaded using LoadLibrary()
60 //# define thread_local __declspec(thread)
61 # warning "CImg plugins cannot be aborted when compiled with this compiler. Please use MinGW, GCC or Clang."
62 /* note that ICC (linux) and Clang are covered by __GNUC__ */
63 # elif defined __GNUC__ || \
64 defined __SUNPRO_C || \
65 defined __xlC__
66 // Clang 3.4 also support SD-6 (feature test macros __cpp_*), but no thread local macro
67 # if defined(__clang__)
68 # if __has_feature(cxx_thread_local)
69 // thread_local was added in Clang 3.3
70 // Still requires libstdc++ from GCC 4.8
71 // For that __GLIBCXX__ isn't good enough
72 // Also the MacOS build of clang does *not* support thread local yet.
73 # define thread_local __thread
74 # define HAVE_THREAD_LOCAL
75 # elif __has_feature(c_thread_local) || __has_extension(c_thread_local)
76 # define thread_local _Thread_local
77 # define HAVE_THREAD_LOCAL
78 # endif
79
80 # elif defined(__GNUC__)
81 //# if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8)
82 //// The C++11 thread_local keyword is supported in GCC only since 4.8
83 //# define thread_local __thread
84 //# endif
85 //# define HAVE_THREAD_LOCAL
86 // __thread became widely supported with GCC 4.7
87 # if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)
88 # define thread_local __thread
89 # define HAVE_THREAD_LOCAL
90 # endif
91 # endif
92 # endif
93 #endif
94
95 #if !defined(HAVE_THREAD_LOCAL)
96 #ifdef WIN32
97 # warning "Most CImg plugins cannot be aborted when compiled with this compiler. Please use MinGW, GCC or Clang."
98 #else
99 // non-Win32 systems should have at least pthread
100 # warning "CImg plugins use pthread-based thread-local storage."
101 #include <assert.h>
102 #include <pthread.h>
103 #define HAVE_PTHREAD
104 #endif
105 #endif //!HAVE_THREAD_LOCAL
106
107
108 //#define CIMG_DEBUG
109
110 // use the locally-downloaded CImg.h
111 //
112 // To download the latest CImg.h, use:
113 // git archive --remote=git://git.code.sf.net/p/gmic/source HEAD:src CImg.h |tar xf -
114 //
115 // CImg.h must at least be the version from Oct 17 2014, commit 9b52016cab3368744ea9f3cc20a3e9b4f0c66eb3
116 // To download, use:
117 // git archive --remote=git://git.code.sf.net/p/gmic/source 9b52016cab3368744ea9f3cc20a3e9b4f0c66eb3:src CImg.h |tar xf -
118 #define cimg_display 0
119 #define cimg_namespace_suffix openfx_misc
120 #ifdef _OPENMP
121 # ifndef cimg_use_openmp
122 # define cimg_use_openmp
123 # endif
124 #endif
125 #define cimg_verbosity 0
126
127 // Abort mechanism:
128 // we have a struct with a thread-local storage that holds the OFX::ImageEffect
129 // for the thread being rendered
130 #if defined(HAVE_THREAD_LOCAL) || defined(HAVE_PTHREAD)
131 #define cimg_abort_test gImageEffectAbort()
132 inline void gImageEffectAbort();
133 #endif
134
135
136 #ifdef cimg_version
137 #error "CImg.h was included before this file"
138 #endif
139
140 #ifdef PLUGIN_PACK_GPL2
141
142 // include the inpaint and nlmeans cimg plugins
143 #if 0 // not necessary since CImg cimmit 7c83bdad65ab7447220b54851a5a1035976777fa
144 namespace cimg_library_openfx_misc {
145 namespace cimg {
146 //! Return the maximum between two values.
147 template<typename t>
148 inline t max(const t& a, const t& b) {
149 return std::max(a,b);
150 }
151 //! Return the minimum between two values.
152 template<typename t>
153 inline t min(const t& a, const t& b) {
154 return std::min(a,b);
155 }
156 }
157 }
158 #endif
159 #define cimg_plugin "Inpaint/inpaint.h"
160 //#define cimg_plugin1 "nlmeans.h"
161 #endif
162
163 CLANG_DIAG_OFF(shorten-64-to-32)
164 #include "CImg.h"
165 CLANG_DIAG_ON(shorten-64-to-32)
166 #define cimg_library cimg_library_suffixed // so that namespace is private, but code requires no change
167
168 typedef float cimgpix_t;
169 typedef float cimgpixfloat_t;
170
171 #define CIMG_ABORTABLE // use abortable versions of CImg functions
172
173 #if defined(HAVE_THREAD_LOCAL)
174 struct tls
175 {
176 static thread_local OFX::ImageEffect *gImageEffect;
177 };
178
179 inline void
gImageEffectAbort()180 gImageEffectAbort()
181 {
182 # ifdef cimg_use_openmp
183 if ( omp_get_thread_num() ) {
184 return;
185 }
186 # endif
187 if ( tls::gImageEffect && tls::gImageEffect->abort() ) {
188 throw cimg_library::CImgAbortException("");
189 }
190 }
191
192 #elif defined(HAVE_PTHREAD)
193 struct tls
194 {
195 // the key is created once and *never* deleted using pthread_key_delete()
196 static pthread_key_t gImageEffect_key;
gImageEffect_key_deletetls197 static void gImageEffect_key_delete(void * arg)
198 {
199 assert (NULL != arg);
200 free(arg);
201 }
gImageEffect_key_createtls202 static void gImageEffect_key_create(void)
203 {
204 int _ret;
205 _ret = pthread_key_create(&(gImageEffect_key), gImageEffect_key_delete);
206 cimg_library::cimg::unused(_ret); /* To get rid of warnings in case of NDEBUG */
207 assert (0 == _ret);
208 }
209 static pthread_once_t gImageEffect_once/* = PTHREAD_ONCE_INIT*/;
210 };
211
212 inline void
gImageEffectAbort()213 gImageEffectAbort()
214 {
215 # ifdef cimg_use_openmp
216 if ( omp_get_thread_num() ) {
217 return;
218 }
219 # endif
220 OFX::ImageEffect **_ptr = (OFX::ImageEffect **)pthread_getspecific(tls::gImageEffect_key);
221 if ( *_ptr && (*_ptr)->abort() ) {
222 throw cimg_library::CImgAbortException("");
223 }
224 }
225 #endif
226
227
228 class CImgFilterPluginHelperBase
229 : public OFX::ImageEffect
230 {
231 public:
232
233 CImgFilterPluginHelperBase(OfxImageEffectHandle handle,
234 bool usesMask, // true if the mask parameter to render should be a single-channel image containing the mask
235 bool supportsComponentRemapping, // true if the number and order of components of the image passed to render() has no importance
236 bool supportsTiles,
237 bool supportsMultiResolution,
238 bool supportsRenderScale,
239 bool defaultUnpremult /* = true*/,
240 bool isFilter /* = true*/);
241
242
243 virtual void changedClip(const OFX::InstanceChangedArgs &args, const std::string &clipName) OVERRIDE;
244 virtual void changedParam(const OFX::InstanceChangedArgs &args, const std::string ¶mName) OVERRIDE;
245 static OFX::PageParamDescriptor* describeInContextBegin(bool sourceIsOptional,
246 OFX::ImageEffectDescriptor &desc,
247 OFX::ContextEnum context,
248 bool supportsRGBA,
249 bool supportsRGB,
250 bool supportsXY,
251 bool supportsAlpha,
252 bool supportsTiles,
253 bool processRGB /* = true*/,
254 bool processAlpha /* = false*/,
255 bool processIsSecret /* = false*/);
256 static void describeInContextEnd(OFX::ImageEffectDescriptor &desc,
257 OFX::ContextEnum context,
258 OFX::PageParamDescriptor* page,
259 bool hasUnpremult = true);
260
261 protected:
262 #ifdef CIMG_DEBUG
printRectI(const char * name,const OfxRectI & rect)263 static void printRectI(const char*name,
264 const OfxRectI& rect)
265 {
266 printf("%s= (%d, %d)-(%d, %d)\n", name, rect.x1, rect.y1, rect.x2, rect.y2);
267 }
268
printRectD(const char * name,const OfxRectD & rect)269 static void printRectD(const char*name,
270 const OfxRectD& rect)
271 {
272 printf("%s= (%g, %g)-(%g, %g)\n", name, rect.x1, rect.y1, rect.x2, rect.y2);
273 }
274
275 #else
276 static void printRectI(const char*,
277 const OfxRectI&) {}
278
279 static void printRectD(const char*,
280 const OfxRectD&) {}
281
282 #endif
283
284
285 void setupAndFill(OFX::PixelProcessorFilterBase & processor,
286 const OfxRectI &renderWindow,
287 void *dstPixelData,
288 const OfxRectI& dstBounds,
289 OFX::PixelComponentEnum dstPixelComponents,
290 int dstPixelComponentCount,
291 OFX::BitDepthEnum dstPixelDepth,
292 int dstRowBytes);
293
294 void setupAndCopy(OFX::PixelProcessorFilterBase & processor,
295 double time,
296 const OfxRectI &renderWindow,
297 const OFX::Image* orig,
298 const OFX::Image* mask,
299 const void *srcPixelData,
300 const OfxRectI& srcBounds,
301 OFX::PixelComponentEnum srcPixelComponents,
302 int srcPixelComponentCount,
303 OFX::BitDepthEnum srcBitDepth,
304 int srcRowBytes,
305 int srcBoundary,
306 void *dstPixelData,
307 const OfxRectI& dstBounds,
308 OFX::PixelComponentEnum dstPixelComponents,
309 int dstPixelComponentCount,
310 OFX::BitDepthEnum dstPixelDepth,
311 int dstRowBytes,
312 bool premult,
313 int premultChannel,
314 double mix,
315 bool maskInvert);
316
317
318 // utility functions
319 static
320 bool maskLineIsZero(const OFX::Image* mask, int x1, int x2, int y, bool maskInvert);
321 static
322 bool maskColumnIsZero(const OFX::Image* mask, int x, int y1, int y2, bool maskInvert);
323
324 protected:
325 // do not need to delete these, the ImageEffect is managing them for us
326 OFX::Clip *_dstClip;
327 OFX::Clip *_srcClip;
328 OFX::Clip *_maskClip;
329
330 // params
331 OFX::BooleanParam* _processR;
332 OFX::BooleanParam* _processG;
333 OFX::BooleanParam* _processB;
334 OFX::BooleanParam* _processA;
335 OFX::BooleanParam* _premult;
336 OFX::ChoiceParam* _premultChannel;
337 OFX::DoubleParam* _mix;
338 OFX::BooleanParam* _maskApply;
339 OFX::BooleanParam* _maskInvert;
340 bool _usesMask; // true if the mask parameter to render() should be a single-channel mask of the same size as the image
341 bool _supportsComponentRemapping; // true if the number and order of components of the image passed to render() has no importance
342 bool _supportsTiles;
343 bool _supportsMultiResolution;
344 bool _supportsRenderScale;
345 bool _defaultUnpremult; //!< unpremult by default
346 OFX::BooleanParam* _premultChanged; // set to true the when user changes premult
347 };
348
349 template <class Params, bool sourceIsOptional>
350 class CImgFilterPluginHelper
351 : public CImgFilterPluginHelperBase
352 {
353 public:
354
CImgFilterPluginHelper(OfxImageEffectHandle handle,bool usesMask,bool supportsComponentRemapping,bool supportsTiles,bool supportsMultiResolution,bool supportsRenderScale,bool defaultUnpremult)355 CImgFilterPluginHelper(OfxImageEffectHandle handle,
356 bool usesMask, // true if the mask parameter to render should be a single-channel image containing the mask
357 bool supportsComponentRemapping, // true if the number and order of components of the image passed to render() has no importance
358 bool supportsTiles,
359 bool supportsMultiResolution,
360 bool supportsRenderScale,
361 bool defaultUnpremult /* = true*/)
362 : CImgFilterPluginHelperBase(handle, usesMask, supportsComponentRemapping, supportsTiles, supportsMultiResolution, supportsRenderScale, defaultUnpremult, /*isFilter=*/ true)
363 {
364 }
365
366 // override the roi call
367 virtual void getRegionsOfInterest(const OFX::RegionsOfInterestArguments &args, OFX::RegionOfInterestSetter &rois) OVERRIDE FINAL;
368 virtual bool getRegionOfDefinition(const OFX::RegionOfDefinitionArguments &args, OfxRectD &rod) OVERRIDE FINAL;
369
370 /* Override the render */
371 virtual void render(const OFX::RenderArguments &args) OVERRIDE FINAL;
372 virtual bool isIdentity(const OFX::IsIdentityArguments &args, OFX::Clip* &identityClip, double &identityTime, int& /*view*/, std::string& /*plane*/) OVERRIDE FINAL;
373
374 // the following functions can be overridden/implemented by the plugin
375 virtual void getValuesAtTime(double time, Params& params) = 0;
376
377 // compute the roi required to compute rect, given params. This roi is then intersected with the image rod.
378 virtual void getRoI(const OfxRectI& rect, const OfxPointD& renderScale, const Params& params, OfxRectI* roi) = 0;
getRegionOfDefinition(const OfxRectI &,const OfxPointD &,const Params &,OfxRectI *)379 virtual bool getRegionOfDefinition(const OfxRectI& /*srcRoD*/,
380 const OfxPointD& /*renderScale*/,
381 const Params& /*params*/,
382 OfxRectI* /*dstRoD*/) { return false; };
383 virtual void render(const OFX::RenderArguments &args,
384 const Params& params,
385 int x1, //!< origin of the image tile
386 int y1, //!< origin of the image tile
387 cimg_library::CImg<cimgpix_t>& mask, //!< in: if the filter uses the mask, a single-channel mask (can be modified by the render func without any side-effect), else an empty image.
388 cimg_library::CImg<cimgpix_t>& cimg, //!< in/out: image
389 int alphaChannel //!< alpha channel in cimg, or -1 if there is no alpha channel
390 ) = 0;
isIdentity(const OFX::IsIdentityArguments &,const Params &)391 virtual bool isIdentity(const OFX::IsIdentityArguments & /*args*/,
392 const Params& /*params*/) { return false; };
393
394 // 0: Black/Dirichlet, 1: Nearest/Neumann, 2: Repeat/Periodic
getBoundary(const Params &)395 virtual int getBoundary(const Params& /*params*/) { return 0; }
396
397 //static void describe(OFX::ImageEffectDescriptor &desc, bool supportsTiles);
398
describeInContextBegin(OFX::ImageEffectDescriptor & desc,OFX::ContextEnum context,bool supportsRGBA,bool supportsRGB,bool supportsXY,bool supportsAlpha,bool supportsTiles,bool processRGB,bool processAlpha,bool processIsSecret)399 static OFX::PageParamDescriptor* describeInContextBegin(OFX::ImageEffectDescriptor &desc,
400 OFX::ContextEnum context,
401 bool supportsRGBA,
402 bool supportsRGB,
403 bool supportsXY,
404 bool supportsAlpha,
405 bool supportsTiles,
406 bool processRGB /* = true*/,
407 bool processAlpha /* = false*/,
408 bool processIsSecret /* = false*/)
409 {
410 return CImgFilterPluginHelperBase::describeInContextBegin(sourceIsOptional,
411 desc,
412 context,
413 supportsRGBA,
414 supportsRGB,
415 supportsXY,
416 supportsAlpha,
417 supportsTiles,
418 processRGB,
419 processAlpha,
420 processIsSecret);
421 }
422 };
423
424
425 template <class Params, bool sourceIsOptional>
426 void
render(const OFX::RenderArguments & args)427 CImgFilterPluginHelper<Params, sourceIsOptional>::render(const OFX::RenderArguments &args)
428 {
429 if ( !_supportsRenderScale && ( (args.renderScale.x != 1.) || (args.renderScale.y != 1.) ) ) {
430 OFX::throwSuiteStatusException(kOfxStatFailed);
431 }
432
433 const double time = args.time;
434 const OfxPointD& renderScale = args.renderScale;
435 const OfxRectI& renderWindow = args.renderWindow;
436 OFX::auto_ptr<OFX::Image> dst( _dstClip->fetchImage(time) );
437 if ( !dst.get() ) {
438 OFX::throwSuiteStatusException(kOfxStatFailed);
439 }
440 if ( (dst->getRenderScale().x != renderScale.x) ||
441 ( dst->getRenderScale().y != renderScale.y) ||
442 ( ( dst->getField() != OFX::eFieldNone) /* for DaVinci Resolve */ && ( dst->getField() != args.fieldToRender) ) ) {
443 setPersistentMessage(OFX::Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
444 OFX::throwSuiteStatusException(kOfxStatFailed);
445 }
446 const OFX::BitDepthEnum dstBitDepth = dst->getPixelDepth();
447 const OFX::PixelComponentEnum dstPixelComponents = dst->getPixelComponents();
448 const int dstPixelComponentCount = dst->getPixelComponentCount();
449 assert(dstBitDepth == OFX::eBitDepthFloat); // only float is supported for now (others are untested)
450
451 OFX::auto_ptr<const OFX::Image> src( ( _srcClip && _srcClip->isConnected() ) ?
452 _srcClip->fetchImage(args.time) : 0 );
453 if ( src.get() ) {
454 OFX::BitDepthEnum srcBitDepth = src->getPixelDepth();
455 OFX::PixelComponentEnum srcPixelComponents = src->getPixelComponents();
456 if ( (srcBitDepth != dstBitDepth) || (srcPixelComponents != dstPixelComponents) ) {
457 OFX::throwSuiteStatusException(kOfxStatErrImageFormat);
458 }
459 if ( (src->getRenderScale().x != renderScale.x) ||
460 ( src->getRenderScale().y != renderScale.y) ||
461 ( ( src->getField() != OFX::eFieldNone) /* for DaVinci Resolve */ && ( src->getField() != args.fieldToRender) ) ) {
462 setPersistentMessage(OFX::Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
463 OFX::throwSuiteStatusException(kOfxStatFailed);
464 }
465 #if 0
466 } else {
467 // src is considered black and transparent, just fill black to dst and return
468 #pragma message WARN("BUG: even when src is black and transparent, the output may not be black (cf. NoiseCImg)")
469 void* dstPixelData = NULL;
470 OfxRectI dstBounds;
471 OFX::PixelComponentEnum dstPixelComponents;
472 OFX::BitDepthEnum dstBitDepth;
473 int dstRowBytes;
474 getImageData(dst.get(), &dstPixelData, &dstBounds, &dstPixelComponents, &dstBitDepth, &dstRowBytes);
475
476 int nComponents = dst->getPixelComponentCount();
477 switch (nComponents) {
478 case 1: {
479 OFX::BlackFiller<float, 1> fred(*this);
480 setupAndFill(fred, renderWindow, dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCOunt, dstBitDepth, dstRowBytes);
481 break;
482 }
483 case 2: {
484 OFX::BlackFiller<float, 2> fred(*this);
485 setupAndFill(fred, renderWindow, dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCOunt, dstBitDepth, dstRowBytes);
486 break;
487 }
488 case 3: {
489 OFX::BlackFiller<float, 3> fred(*this);
490 setupAndFill(fred, renderWindow, dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCOunt, dstBitDepth, dstRowBytes);
491 break;
492 }
493 case 4: {
494 OFX::BlackFiller<float, 4> fred(*this);
495 setupAndFill(fred, renderWindow, dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCOunt, dstBitDepth, dstRowBytes);
496 break;
497 }
498 default:
499 assert(false);
500 break;
501 } // switch
502
503 return;
504 #endif
505 }
506
507 const void *srcPixelData;
508 OfxRectI srcBounds;
509 OfxRectI srcRoD;
510 OFX::PixelComponentEnum srcPixelComponents;
511 int srcPixelComponentCount;
512 OFX::BitDepthEnum srcBitDepth;
513 //srcPixelBytes = getPixelBytes(srcPixelComponents, srcBitDepth);
514 int srcRowBytes;
515 if ( !src.get() ) {
516 srcPixelData = NULL;
517 srcBounds.x1 = srcBounds.y1 = srcBounds.x2 = srcBounds.y2 = 0;
518 srcRoD.x1 = srcRoD.y1 = srcRoD.x2 = srcRoD.y2 = 0;
519 srcPixelComponents = _srcClip ? _srcClip->getPixelComponents() : OFX::ePixelComponentNone;
520 srcPixelComponentCount = _srcClip ? _srcClip->getPixelComponentCount() : 0;
521 srcBitDepth = _srcClip ? _srcClip->getPixelDepth() : OFX::eBitDepthNone;
522 srcRowBytes = 0;
523 } else {
524 assert(_srcClip);
525 srcPixelData = src->getPixelData();
526 srcBounds = src->getBounds();
527 // = src->getRegionOfDefinition(); // Nuke's image RoDs are wrong
528 if (_supportsTiles) {
529 OFX::Coords::toPixelEnclosing(_srcClip->getRegionOfDefinition(time), args.renderScale, _srcClip->getPixelAspectRatio(), &srcRoD);
530 } else {
531 // In Sony Catalyst Edit, clipGetRegionOfDefinition returns the RoD in pixels instead of canonical coordinates.
532 // in hosts that do not support tiles (such as Sony Catalyst Edit), the image RoD is the image Bounds anyway.
533 srcRoD = srcBounds;
534 }
535 srcPixelComponents = src->getPixelComponents();
536 srcPixelComponentCount = src->getPixelComponentCount();
537 srcBitDepth = src->getPixelDepth();
538 srcRowBytes = src->getRowBytes();
539 }
540
541 void *dstPixelData = dst->getPixelData();
542 const OfxRectI& dstBounds = dst->getBounds();
543 OfxRectI dstRoD; // = dst->getRegionOfDefinition(); // Nuke's image RoDs are wrong
544 if (_supportsTiles) {
545 OFX::Coords::toPixelEnclosing(_dstClip->getRegionOfDefinition(time), args.renderScale, _dstClip->getPixelAspectRatio(), &dstRoD);
546 } else {
547 // In Sony Catalyst Edit, clipGetRegionOfDefinition returns the RoD in pixels instead of canonical coordinates.
548 // in hosts that do not support tiles (such as Sony Catalyst Edit), the image RoD is the image Bounds anyway.
549 dstRoD = dstBounds;
550 }
551 //const OFX::PixelComponentEnum dstPixelComponents = dst->getPixelComponents();
552 //const OFX::BitDepthEnum dstBitDepth = dst->getPixelDepth();
553 const int dstRowBytes = dst->getRowBytes();
554
555 if (!_supportsTiles) {
556 // http://openfx.sourceforge.net/Documentation/1.3/ofxProgrammingReference.html#kOfxImageEffectPropSupportsTiles
557 // If a clip or plugin does not support tiled images, then the host should supply full RoD images to the effect whenever it fetches one.
558 if ( src.get() ) {
559 assert(srcRoD.x1 == srcBounds.x1);
560 assert(srcRoD.x2 == srcBounds.x2);
561 assert(srcRoD.y1 == srcBounds.y1);
562 assert(srcRoD.y2 == srcBounds.y2); // crashes on Natron if kSupportsTiles=0 & kSupportsMultiResolution=1
563 }
564 assert(dstRoD.x1 == dstBounds.x1);
565 assert(dstRoD.x2 == dstBounds.x2);
566 assert(dstRoD.y1 == dstBounds.y1);
567 assert(dstRoD.y2 == dstBounds.y2); // crashes on Natron if kSupportsTiles=0 & kSupportsMultiResolution=1
568 }
569 if (!_supportsMultiResolution) {
570 // http://openfx.sourceforge.net/Documentation/1.3/ofxProgrammingReference.html#kOfxImageEffectPropSupportsMultiResolution
571 // Multiple resolution images mean...
572 // input and output images can be of any size
573 // input and output images can be offset from the origin
574 if ( src.get() ) {
575 assert(srcRoD.x1 == 0);
576 assert(srcRoD.y1 == 0);
577 assert(srcRoD.x1 == dstRoD.x1);
578 assert(srcRoD.x2 == dstRoD.x2);
579 assert(srcRoD.y1 == dstRoD.y1);
580 assert(srcRoD.y2 == dstRoD.y2); // crashes on Natron if kSupportsMultiResolution=0
581 }
582 }
583
584 bool processR, processG, processB, processA;
585 if (_processR) {
586 _processR->getValueAtTime(time, processR);
587 _processG->getValueAtTime(time, processG);
588 _processB->getValueAtTime(time, processB);
589 _processA->getValueAtTime(time, processA);
590 } else {
591 processR = processG = processB = processA = true;
592 }
593 bool premult = _premult ? _premult->getValueAtTime(time) : false;
594 int premultChannel = _premultChannel ? _premultChannel->getValueAtTime(time) : 3;
595 double mix = _mix->getValueAtTime(time);
596 bool maskInvert = _maskInvert->getValueAtTime(time);
597 if (!processR && !processG && !processB) {
598 // no need to (un)premult if we don't change colors
599 premult = false;
600 }
601
602 bool doMasking = ( ( !_maskApply || _maskApply->getValueAtTime(args.time) ) && _maskClip && _maskClip->isConnected() );
603 OFX::auto_ptr<const OFX::Image> mask(doMasking ? _maskClip->fetchImage(time) : 0);
604 OfxRectI processWindow = renderWindow; //!< the window where pixels have to be computed (may be smaller than renderWindow if mask is zero on the borders)
605
606 if (mix == 0.) {
607 // no processing at all
608 processWindow.x2 = processWindow.x1;
609 processWindow.y2 = processWindow.y1;
610 }
611 if ( mask.get() ) {
612 if ( (mask->getRenderScale().x != renderScale.x) ||
613 ( mask->getRenderScale().y != renderScale.y) ||
614 ( ( mask->getField() != OFX::eFieldNone) /* for DaVinci Resolve */ && ( mask->getField() != args.fieldToRender) ) ) {
615 setPersistentMessage(OFX::Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
616 OFX::throwSuiteStatusException(kOfxStatFailed);
617 }
618
619 if (_supportsTiles) {
620 // shrink the processWindow at much as possible
621 // top
622 while ( processWindow.y2 > processWindow.y1 && maskLineIsZero(mask.get(), processWindow.x1, processWindow.x2, processWindow.y2 - 1, maskInvert) ) {
623 --processWindow.y2;
624 }
625 // bottom
626 while ( processWindow.y2 > processWindow.y1 && maskLineIsZero(mask.get(), processWindow.x1, processWindow.x2, processWindow.y1, maskInvert) ) {
627 ++processWindow.y1;
628 }
629 // left
630 while ( processWindow.x2 > processWindow.x1 && maskColumnIsZero(mask.get(), processWindow.x1, processWindow.y1, processWindow.y2, maskInvert) ) {
631 ++processWindow.x1;
632 }
633 // right
634 while ( processWindow.x2 > processWindow.x1 && maskColumnIsZero(mask.get(), processWindow.x2 - 1, processWindow.y1, processWindow.y2, maskInvert) ) {
635 --processWindow.x2;
636 }
637 }
638 }
639
640 Params params;
641 getValuesAtTime(time, params);
642 int srcBoundary = getBoundary(params);
643 assert(0 <= srcBoundary && srcBoundary <= 2);
644
645 // copy areas of renderWindow that are not within processWindow to dst
646
647 OfxRectI copyWindowN, copyWindowS, copyWindowE, copyWindowW;
648 // top
649 copyWindowN.x1 = renderWindow.x1;
650 copyWindowN.x2 = renderWindow.x2;
651 copyWindowN.y1 = processWindow.y2;
652 copyWindowN.y2 = renderWindow.y2;
653 // bottom
654 copyWindowS.x1 = renderWindow.x1;
655 copyWindowS.x2 = renderWindow.x2;
656 copyWindowS.y1 = renderWindow.y1;
657 copyWindowS.y2 = processWindow.y1;
658 // left
659 copyWindowW.x1 = renderWindow.x1;
660 copyWindowW.x2 = processWindow.x1;
661 copyWindowW.y1 = processWindow.y1;
662 copyWindowW.y2 = processWindow.y2;
663 // right
664 copyWindowE.x1 = processWindow.x2;
665 copyWindowE.x2 = renderWindow.x2;
666 copyWindowE.y1 = processWindow.y1;
667 copyWindowE.y2 = processWindow.y2;
668 {
669 OFX::auto_ptr<OFX::PixelProcessorFilterBase> fred;
670 if (dstPixelComponentCount == 4) {
671 fred.reset( new OFX::PixelCopier<float, 4>(*this) );
672 } else if (dstPixelComponentCount == 3) {
673 fred.reset( new OFX::PixelCopier<float, 3>(*this) );
674 } else if (dstPixelComponentCount == 2) {
675 fred.reset( new OFX::PixelCopier<float, 2>(*this) );
676 } else if (dstPixelComponentCount == 1) {
677 fred.reset( new OFX::PixelCopier<float, 1>(*this) );
678 }
679 assert( fred.get() );
680 if ( fred.get() ) {
681 setupAndCopy(*fred, time, copyWindowN, src.get(), mask.get(),
682 srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcBitDepth, srcRowBytes, srcBoundary,
683 dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes,
684 premult, premultChannel, mix, maskInvert);
685 setupAndCopy(*fred, time, copyWindowS, src.get(), mask.get(),
686 srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcBitDepth, srcRowBytes, srcBoundary,
687 dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes,
688 premult, premultChannel, mix, maskInvert);
689 setupAndCopy(*fred, time, copyWindowW, src.get(), mask.get(),
690 srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcBitDepth, srcRowBytes, srcBoundary,
691 dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes,
692 premult, premultChannel, mix, maskInvert);
693 setupAndCopy(*fred, time, copyWindowE, src.get(), mask.get(),
694 srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcBitDepth, srcRowBytes, srcBoundary,
695 dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes,
696 premult, premultChannel, mix, maskInvert);
697 }
698 }
699
700 printRectI("srcRoD", srcRoD);
701 printRectI("srcBounds", srcBounds);
702 printRectI("dstRoD", dstRoD);
703 printRectI("dstBounds", dstBounds);
704 printRectI("renderWindow", renderWindow);
705 printRectI("processWindow", processWindow);
706
707 if ( OFX::Coords::rectIsEmpty(processWindow) ) {
708 // the area that actually has to be processed is empty, the job is finished!
709 return;
710 }
711 assert(mix != 0.); // mix == 0. should give an empty processWindow
712
713 // compute the src ROI (should be consistent with getRegionsOfInterest())
714 OfxRectI srcRoI;
715 getRoI(processWindow, renderScale, params, &srcRoI);
716 printRectI("srcRoI", srcRoI);
717 // intersect against the destination RoD
718 bool intersect = OFX::Coords::rectIntersection(srcRoI, dstRoD, &srcRoI);
719 printRectI("srcRoIIntersected", srcRoI);
720 if (!intersect) {
721 src.reset(NULL);
722 srcPixelData = NULL;
723 srcBounds.x1 = srcBounds.y1 = srcBounds.x2 = srcBounds.y2 = 0;
724 srcRoD.x1 = srcRoD.y1 = srcRoD.x2 = srcRoD.y2 = 0;
725 srcPixelComponents = _srcClip->getPixelComponents();
726 srcPixelComponentCount = _srcClip->getPixelComponentCount();
727 srcBitDepth = _srcClip->getPixelDepth();
728 srcRowBytes = 0;
729 }
730
731 // The following checks may be wrong, because the srcRoI may be outside of the region of definition of src.
732 // It is not an error: areas outside of srcRoD should be considered black and transparent.
733 // IF THE FOLLOWING CODE HAS TO BE DISACTIVATED, PLEASE COMMENT WHY.
734 // This was disactivated by commit c47d07669b78a71960b204989d9c36f746d14a4c, then reactivated.
735 // DISACTIVATED AGAIN by FD 9/12/2014: boundary conditions are now handled by pixelcopier, and interstection with dstRoD was added above
736 #if 0 //def CIMGFILTER_INSTERSECT_ROI
737 OFX::Coords::rectIntersection(srcRoI, srcRoD, &srcRoI);
738 // the resulting ROI should be within the src bounds, or it means that the host didn't take into account the region of interest (see getRegionsOfInterest() )
739 assert(srcBounds.x1 <= srcRoI.x1 && srcRoI.x2 <= srcBounds.x2 &&
740 srcBounds.y1 <= srcRoI.y1 && srcRoI.y2 <= srcBounds.y2);
741 if ( (srcBounds.x1 > srcRoI.x1) || (srcRoI.x2 > srcBounds.x2) ||
742 ( srcBounds.y1 > srcRoI.y1) || ( srcRoI.y2 > srcBounds.y2) ) {
743 OFX::throwSuiteStatusException(kOfxStatFailed);
744 }
745
746 if ( doMasking && (mix != 1.) ) {
747 // the renderWindow should also be contained within srcBounds, since we are mixing
748 assert(srcBounds.x1 <= renderWindow.x1 && renderWindow.x2 <= srcBounds.x2 &&
749 srcBounds.y1 <= renderWindow.y1 && renderWindow.y2 <= srcBounds.y2);
750 if ( (srcBounds.x1 > renderWindow.x1) || (renderWindow.x2 > srcBounds.x2) ||
751 ( srcBounds.y1 > renderWindow.y1) || ( renderWindow.y2 > srcBounds.y2) ) {
752 OFX::throwSuiteStatusException(kOfxStatFailed);
753 }
754 }
755 #endif
756
757 #ifdef cimg_use_openmp
758 // set the number of OpenMP threads to a reasonable value
759 // (but remember that the OpenMP threads are not counted my the multithread suite)
760 {
761 unsigned int ncpus = OFX::MultiThread::getNumCPUs();
762 omp_set_num_threads( std::max(1u, ncpus) );
763 //printf("ncpus=%u\n", ncpus);
764 }
765 #endif
766
767 // from here on, we do the following steps:
768 // 1- copy & unpremult all channels from srcRoI, from src to a tmp image of size srcRoI
769 // 2- extract channels to be processed from tmp to a cimg of size srcRoI (and do the interleaved to coplanar conversion)
770 // 3- process the cimg
771 // 4- copy back the processed channels from the cImg to tmp. only processWindow has to be copied
772 // 5- copy+premult+max+mix tmp to dst (only processWindow)
773
774 //////////////////////////////////////////////////////////////////////////////////////////
775 // 1- copy & unpremult all channels from srcRoI, from src to a tmp image of size srcRoI
776 const OfxRectI tmpBounds = srcRoI;
777 const OFX::PixelComponentEnum tmpPixelComponents = srcPixelData ? srcPixelComponents : dstPixelComponents;
778 const int tmpPixelComponentCount = srcPixelData ? srcPixelComponentCount : dstPixelComponentCount;
779 const OFX::BitDepthEnum tmpBitDepth = OFX::eBitDepthFloat;
780 const int tmpWidth = tmpBounds.x2 - tmpBounds.x1;
781 const int tmpHeight = tmpBounds.y2 - tmpBounds.y1;
782 const size_t tmpRowBytes = (size_t)tmpPixelComponentCount * getComponentBytes(tmpBitDepth) * tmpWidth;
783 size_t tmpSize = tmpRowBytes * tmpHeight;
784 OFX::auto_ptr<OFX::ImageMemory> tmpData;
785 float *tmpPixelData = NULL;
786 if (tmpSize > 0) {
787 tmpData.reset( new OFX::ImageMemory(tmpSize, this) );
788 tmpPixelData = (float*)tmpData->lock();
789
790 OFX::auto_ptr<OFX::PixelProcessorFilterBase> fred;
791 if ( !src.get() ) {
792 // no src, fill with black & transparent
793 fred.reset( new OFX::BlackFiller<float>(*this, dstPixelComponentCount) );
794 } else {
795 if (dstPixelComponents == OFX::ePixelComponentRGBA) {
796 fred.reset( new OFX::PixelCopierUnPremult<float, 4, 1, float, 4, 1>(*this) );
797 } else if (dstPixelComponentCount == 4) {
798 // just copy, no premult
799 fred.reset( new OFX::PixelCopier<float, 4>(*this) );
800 } else if (dstPixelComponentCount == 3) {
801 // just copy, no premult
802 fred.reset( new OFX::PixelCopier<float, 3>(*this) );
803 } else if (dstPixelComponentCount == 2) {
804 // just copy, no premult
805 fred.reset( new OFX::PixelCopier<float, 2>(*this) );
806 } else if (dstPixelComponentCount == 1) {
807 // just copy, no premult
808 fred.reset( new OFX::PixelCopier<float, 1>(*this) );
809 }
810 }
811 assert( fred.get() );
812 if ( fred.get() ) {
813 setupAndCopy(*fred, time, srcRoI, src.get(), mask.get(),
814 srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcBitDepth, srcRowBytes, srcBoundary,
815 tmpPixelData, tmpBounds, tmpPixelComponents, tmpPixelComponentCount, tmpBitDepth, tmpRowBytes,
816 premult, premultChannel, mix, maskInvert);
817 }
818 }
819 if ( abort() ) {
820 return;
821 }
822
823 //////////////////////////////////////////////////////////////////////////////////////////
824 // 2- extract channels to be processed from tmp to a cimg of size srcRoI (and do the interleaved to coplanar conversion)
825
826 // allocate the cimg data to hold the src ROI
827 int cimgSpectrum;
828 if (!_supportsComponentRemapping) {
829 cimgSpectrum = tmpPixelComponentCount;
830 } else {
831 switch (tmpPixelComponents) {
832 case OFX::ePixelComponentAlpha:
833 cimgSpectrum = (int)processA;
834 break;
835 case OFX::ePixelComponentXY:
836 cimgSpectrum = (int)processR + (int)processG + (int) processB;
837 break;
838 case OFX::ePixelComponentRGB:
839 cimgSpectrum = (int)processR + (int)processG + (int) processB;
840 break;
841 case OFX::ePixelComponentRGBA:
842 cimgSpectrum = (int)processR + (int)processG + (int) processB + (int)processA;
843 break;
844 default:
845 cimgSpectrum = 0;
846 }
847 }
848 const int cimgWidth = srcRoI.x2 - srcRoI.x1;
849 const int cimgHeight = srcRoI.y2 - srcRoI.y1;
850 const size_t cimgSize = cimgWidth * cimgHeight * cimgSpectrum * sizeof(cimgpix_t);
851 std::vector<int> srcChannel(cimgSpectrum, -1);
852
853 int alphaChannel = -1;
854 if (!_supportsComponentRemapping) {
855 for (int c = 0; c < tmpPixelComponentCount; ++c) {
856 srcChannel[c] = c;
857 }
858 assert(tmpPixelComponentCount == cimgSpectrum);
859 } else {
860 if (tmpPixelComponentCount == 1) {
861 if (processA) {
862 assert(cimgSpectrum == 1);
863 srcChannel[0] = 0;
864 alphaChannel = 0;
865 } else {
866 assert(cimgSpectrum == 0);
867 }
868 } else {
869 int c = 0;
870 if (processR) {
871 srcChannel[c] = 0;
872 ++c;
873 }
874 if (processG) {
875 srcChannel[c] = 1;
876 ++c;
877 }
878 if (processB) {
879 srcChannel[c] = 2;
880 ++c;
881 }
882 if ( processA && (tmpPixelComponentCount >= 4) ) {
883 srcChannel[c] = 3;
884 alphaChannel = c;
885 ++c;
886 }
887 assert(c == cimgSpectrum);
888 }
889 }
890 if (cimgSize) { // may be zero if no channel is processed
891 OFX::auto_ptr<OFX::ImageMemory> cimgData( new OFX::ImageMemory(cimgSize, this) );
892 cimgpix_t *cimgPixelData = (cimgpix_t*)cimgData->lock();
893 cimg_library::CImg<cimgpix_t> maskcimg;
894 cimg_library::CImg<cimgpix_t> cimg(cimgPixelData, cimgWidth, cimgHeight, 1, cimgSpectrum, true);
895
896 if (tmpSize > 0) {
897 for (int c = 0; c < cimgSpectrum; ++c) {
898 cimgpix_t *dst = cimg.data(0, 0, 0, c);
899 const float *src = tmpPixelData + srcChannel[c];
900 for (unsigned int siz = cimgWidth * cimgHeight; siz; --siz, src += tmpPixelComponentCount, ++dst) {
901 *dst = *src;
902 }
903 }
904 } else {
905 cimg.fill(0);
906 }
907 if ( abort() ) {
908 return;
909 }
910
911 assert(sizeof(cimgpix_t) == 4); // the following only works for float pix
912 if (_usesMask) {
913 maskcimg.assign(cimgWidth, cimgHeight, 1, 1);
914 if (!mask.get()) {
915 maskcimg.fill(1.);
916 } else {
917 copyPixels(*this,
918 srcRoI,
919 mask.get(),
920 maskcimg.data(),
921 srcRoI,
922 OFX::ePixelComponentAlpha,
923 1,
924 OFX::eBitDepthFloat,
925 cimgWidth * sizeof(float));
926 if(maskInvert) {
927 maskcimg *= -1;
928 maskcimg += 1;
929 }
930 }
931 }
932
933 //////////////////////////////////////////////////////////////////////////////////////////
934 // 3- process the cimg
935 printRectI("render srcRoI", srcRoI);
936 #if defined(HAVE_THREAD_LOCAL) || defined(HAVE_PTHREAD)
937 # if defined(HAVE_THREAD_LOCAL)
938 tls::gImageEffect = this;
939 # else
940 OFX::ImageEffect **_ptr = (OFX::ImageEffect **)pthread_getspecific(tls::gImageEffect_key);
941 assert (NULL != _ptr);
942 *_ptr = this;
943 # endif
944 try {
945 render(args, params, srcRoI.x1, srcRoI.y1, maskcimg, cimg, alphaChannel);
946 } catch (cimg_library::CImgAbortException) {
947 # if defined(HAVE_THREAD_LOCAL)
948 tls::gImageEffect = 0;
949 # else
950 *_ptr = 0;
951 # endif
952
953 return;
954 }
955
956 # if defined(HAVE_THREAD_LOCAL)
957 tls::gImageEffect = 0;
958 # else
959 *_ptr = 0;
960 # endif
961 #else
962 render(args, params, srcRoI.x1, srcRoI.y1, maskcimg, cimg, alphaChannel);
963 #endif
964 // check that the dimensions didn't change
965 assert(cimg.width() == cimgWidth && cimg.height() == cimgHeight && cimg.depth() == 1 && cimg.spectrum() == cimgSpectrum);
966 if ( abort() ) {
967 return;
968 }
969
970 //////////////////////////////////////////////////////////////////////////////////////////
971 // 4- copy back the processed channels from the cImg to tmp. only processWindow has to be copied
972
973 // We copy the whole srcRoI. This could be optimized to copy only renderWindow
974 for (int c = 0; c < cimgSpectrum; ++c) {
975 const cimgpix_t *src = cimg.data(0, 0, 0, c);
976 float *dst = tmpPixelData + srcChannel[c];
977 for (unsigned int siz = cimgWidth * cimgHeight; siz; --siz, ++src, dst += tmpPixelComponentCount) {
978 *dst = *src;
979 }
980 }
981 }
982 if ( abort() ) {
983 return;
984 }
985
986 //////////////////////////////////////////////////////////////////////////////////////////
987 // 5- copy+premult+max+mix tmp to dst (only processWindow)
988
989 {
990 OFX::auto_ptr<OFX::PixelProcessorFilterBase> fred;
991 if (dstPixelComponents == OFX::ePixelComponentRGBA) {
992 fred.reset( new OFX::PixelCopierPremultMaskMix<float, 4, 1, float, 4, 1>(*this) );
993 } else if (dstPixelComponentCount == 4) {
994 // just copy, no premult
995 if (doMasking) {
996 fred.reset( new OFX::PixelCopierMaskMix<float, 4, 1, true>(*this) );
997 } else {
998 fred.reset( new OFX::PixelCopierMaskMix<float, 4, 1, false>(*this) );
999 }
1000 } else if (dstPixelComponentCount == 3) {
1001 // just copy, no premult
1002 if (doMasking) {
1003 fred.reset( new OFX::PixelCopierMaskMix<float, 3, 1, true>(*this) );
1004 } else {
1005 fred.reset( new OFX::PixelCopierMaskMix<float, 3, 1, false>(*this) );
1006 }
1007 } else if (dstPixelComponentCount == 2) {
1008 // just copy, no premult
1009 if (doMasking) {
1010 fred.reset( new OFX::PixelCopierMaskMix<float, 2, 1, true>(*this) );
1011 } else {
1012 fred.reset( new OFX::PixelCopierMaskMix<float, 2, 1, false>(*this) );
1013 }
1014 } else if (dstPixelComponentCount == 1) {
1015 // just copy, no premult
1016 assert(srcPixelComponents == OFX::ePixelComponentAlpha);
1017 if (doMasking) {
1018 fred.reset( new OFX::PixelCopierMaskMix<float, 1, 1, true>(*this) );
1019 } else {
1020 fred.reset( new OFX::PixelCopierMaskMix<float, 1, 1, false>(*this) );
1021 }
1022 }
1023 assert( fred.get() );
1024 if ( fred.get() ) {
1025 setupAndCopy(*fred, time, processWindow, src.get(), mask.get(),
1026 tmpPixelData, tmpBounds, tmpPixelComponents, tmpPixelComponentCount, tmpBitDepth, tmpRowBytes, 0,
1027 dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes,
1028 premult, premultChannel, mix, maskInvert);
1029 }
1030 }
1031
1032 //////////////////////////////////////////////////////////////////////////////////////////
1033 // done!
1034 } // >::render
1035
1036 // override the roi call
1037 // Required if the plugin requires a region from the inputs which is different from the rendered region of the output.
1038 // (this is the case here)
1039 template <class Params, bool sourceIsOptional>
1040 void
getRegionsOfInterest(const OFX::RegionsOfInterestArguments & args,OFX::RegionOfInterestSetter & rois)1041 CImgFilterPluginHelper<Params, sourceIsOptional>::getRegionsOfInterest(const OFX::RegionsOfInterestArguments &args,
1042 OFX::RegionOfInterestSetter &rois)
1043 {
1044 if ( !_supportsRenderScale && ( (args.renderScale.x != 1.) || (args.renderScale.y != 1.) ) ) {
1045 OFX::throwSuiteStatusException(kOfxStatFailed);
1046 }
1047 const double time = args.time;
1048 const OfxRectD& regionOfInterest = args.regionOfInterest;
1049 OfxRectD srcRoI;
1050 double mix = 1.;
1051 bool doMasking = ( ( !_maskApply || _maskApply->getValueAtTime(args.time) ) && _maskClip && _maskClip->isConnected() );
1052 if (doMasking) {
1053 _mix->getValueAtTime(time, mix);
1054 if (mix == 0.) {
1055 // identity transform
1056 //srcRoI = regionOfInterest;
1057
1058 //rois.setRegionOfInterest(*_srcClip, srcRoI);
1059 return;
1060 }
1061 }
1062
1063 Params params;
1064 getValuesAtTime(args.time, params);
1065
1066 double pixelaspectratio = ( _srcClip && _srcClip->isConnected() ) ? _srcClip->getPixelAspectRatio() : 1.;
1067 OfxRectI rectPixel;
1068 OFX::Coords::toPixelEnclosing(regionOfInterest, args.renderScale, pixelaspectratio, &rectPixel);
1069 OfxRectI srcRoIPixel;
1070 getRoI(rectPixel, args.renderScale, params, &srcRoIPixel);
1071 OFX::Coords::toCanonical(srcRoIPixel, args.renderScale, pixelaspectratio, &srcRoI);
1072
1073 if ( doMasking && (mix != 1.) ) {
1074 // for masking or mixing, we also need the source image.
1075 // compute the bounding box with the default ROI
1076 OFX::Coords::rectBoundingBox(srcRoI, regionOfInterest, &srcRoI);
1077 }
1078
1079 // no need to set it on mask (the default ROI is OK)
1080 rois.setRegionOfInterest(*_srcClip, srcRoI);
1081 }
1082
1083 template <class Params, bool sourceIsOptional>
1084 bool
getRegionOfDefinition(const OFX::RegionOfDefinitionArguments & args,OfxRectD & rod)1085 CImgFilterPluginHelper<Params, sourceIsOptional>::getRegionOfDefinition(const OFX::RegionOfDefinitionArguments &args,
1086 OfxRectD &rod)
1087 {
1088 if ( !_supportsRenderScale && ( (args.renderScale.x != 1.) || (args.renderScale.y != 1.) ) ) {
1089 OFX::throwSuiteStatusException(kOfxStatFailed);
1090 }
1091 Params params;
1092 getValuesAtTime(args.time, params);
1093
1094 OfxRectI srcRoDPixel = {0, 0, 0, 0};
1095 {
1096 double pixelaspectratio = ( _srcClip && _srcClip->isConnected() ) ? _srcClip->getPixelAspectRatio() : 1.;
1097 if (_srcClip) {
1098 OFX::Coords::toPixelEnclosing(_srcClip->getRegionOfDefinition(args.time), args.renderScale, pixelaspectratio, &srcRoDPixel);
1099 }
1100 }
1101 OfxRectI rodPixel;
1102 bool ret = getRegionOfDefinition(srcRoDPixel, args.renderScale, params, &rodPixel);
1103 if (ret) {
1104 double pixelaspectratio = _dstClip ? _dstClip->getPixelAspectRatio() : 1.;
1105 OFX::Coords::toCanonical(rodPixel, args.renderScale, pixelaspectratio, &rod);
1106
1107 return true;
1108 }
1109
1110 return false;
1111 }
1112
1113 template <class Params, bool sourceIsOptional>
1114 bool
isIdentity(const OFX::IsIdentityArguments & args,OFX::Clip * & identityClip,double &,int &,std::string &)1115 CImgFilterPluginHelper<Params, sourceIsOptional>::isIdentity(const OFX::IsIdentityArguments &args,
1116 OFX::Clip * &identityClip,
1117 double & /*identityTime*/
1118 , int& /*view*/, std::string& /*plane*/)
1119 {
1120 if ( !_supportsRenderScale && ( (args.renderScale.x != 1.) || (args.renderScale.y != 1.) ) ) {
1121 OFX::throwSuiteStatusException(kOfxStatFailed);
1122 }
1123 const double time = args.time;
1124 double mix;
1125 _mix->getValueAtTime(time, mix);
1126 if (mix == 0.) {
1127 identityClip = _srcClip;
1128
1129 return true;
1130 }
1131
1132 if (_processR) {
1133 bool processR;
1134 bool processG;
1135 bool processB;
1136 bool processA;
1137 _processR->getValueAtTime(args.time, processR);
1138 _processG->getValueAtTime(args.time, processG);
1139 _processB->getValueAtTime(args.time, processB);
1140 _processA->getValueAtTime(args.time, processA);
1141 if (!processR && !processG && !processB && !processA) {
1142 identityClip = _srcClip;
1143
1144 return true;
1145 }
1146 }
1147
1148 Params params;
1149 getValuesAtTime(time, params);
1150 if ( isIdentity(args, params) ) {
1151 identityClip = _srcClip;
1152
1153 return true;
1154 }
1155
1156 bool doMasking = ( ( !_maskApply || _maskApply->getValueAtTime(args.time) ) && _maskClip && _maskClip->isConnected() );
1157 if (doMasking) {
1158 bool maskInvert;
1159 _maskInvert->getValueAtTime(args.time, maskInvert);
1160 if (!maskInvert) {
1161 OfxRectI maskRoD;
1162 if (OFX::getImageEffectHostDescription()->supportsMultiResolution) {
1163 // In Sony Catalyst Edit, clipGetRegionOfDefinition returns the RoD in pixels instead of canonical coordinates.
1164 // In hosts that do not support multiResolution (e.g. Sony Catalyst Edit), all inputs have the same RoD anyway.
1165 OFX::Coords::toPixelEnclosing(_maskClip->getRegionOfDefinition(args.time), args.renderScale, _maskClip->getPixelAspectRatio(), &maskRoD);
1166 // effect is identity if the renderWindow doesn't intersect the mask RoD
1167 if ( !OFX::Coords::rectIntersection<OfxRectI>(args.renderWindow, maskRoD, 0) ) {
1168 identityClip = _srcClip;
1169
1170 return true;
1171 }
1172 }
1173 }
1174 }
1175
1176 return false;
1177 } // >::isIdentity
1178
1179 // functions for a reproductible random number generator (used in CImgNoise.cpp and CImgPlasma.cpp)
1180 inline unsigned int
cimg_hash(unsigned int a)1181 cimg_hash(unsigned int a)
1182 {
1183 a = (a ^ 61) ^ (a >> 16);
1184 a = a + (a << 3);
1185 a = a ^ (a >> 4);
1186 a = a * 0x27d4eb2d;
1187 a = a ^ (a >> 15);
1188
1189 return a;
1190 }
1191
1192 // returns a value from 0 to 0x100000000ULL excluded
1193 inline unsigned int
cimg_irand(unsigned int seed,int x,int y,int nComponents)1194 cimg_irand(unsigned int seed, int x, int y, int nComponents)
1195 {
1196 return cimg_hash(cimg_hash(cimg_hash(seed ^ x) ^ y) ^ nComponents);
1197 }
1198
1199 inline double
cimg_rand(unsigned int seed,int x,int y,int nComponents,const double val_min,const double val_max)1200 cimg_rand(unsigned int seed, int x, int y, int nComponents, const double val_min, const double val_max)
1201 {
1202 const double val = cimg_irand(seed, x, y, nComponents) / ( (double)0x100000000ULL );
1203 return val_min + (val_max - val_min)*val;
1204 }
1205
1206 //! Return a random variable uniformely distributed between [0,val_max].
1207 /**
1208 **/
1209 inline double
1210 cimg_rand(unsigned int seed, int x, int y, int nComponents, const double val_max = 1.)
1211 {
1212 return cimg_rand(seed, x, y, nComponents, 0, val_max);
1213 }
1214
1215 //! Return a random variable following a gaussian distribution and a standard deviation of 1.
1216 /**
1217 **/
1218 inline double
cimg_grand(unsigned int seed,int x,int y,int nComponents)1219 cimg_grand(unsigned int seed, int x, int y, int nComponents)
1220 {
1221 double x1, w;
1222 unsigned int s = seed;
1223 do {
1224 unsigned int r1 = cimg_irand(s, x, y, nComponents);
1225 unsigned int r2 = cimg_irand(r1, x, y, nComponents);
1226 s = r2;
1227
1228 const double x2 = 2 * (double) r2 / ( (double)0x100000000ULL ) - 1.;
1229 x1 = 2 * (double) r1 / ( (double)0x100000000ULL ) - 1.;
1230 w = x1*x1 + x2*x2;
1231 } while (w<=0 || w>=1.0);
1232 return x1*std::sqrt((-2*std::log(w))/w);
1233 }
1234
1235 //! Return a random variable following a Poisson distribution of parameter z.
1236 /**
1237 **/
1238 inline unsigned int
cimg_prand(unsigned int seed,int x,int y,int nComponents,const double z)1239 cimg_prand(unsigned int seed, int x, int y, int nComponents, const double z)
1240 {
1241 if (z<=1.0e-10) {
1242 return 0;
1243 }
1244 if (z>100) {
1245 return (unsigned int)((std::sqrt(z) * cimg_grand(seed, x, y, nComponents)) + z);
1246 }
1247 unsigned int k = 0;
1248 const double y1 = std::exp(-z);
1249 for (double s = 1.0; s >= y1; ++k) {
1250 s *= cimg_rand(seed+1, x, y, nComponents);
1251 }
1252 return k > 0 ? k - 1 : 0;
1253 }
1254
1255 #endif // ifndef Misc_CImgFilter_h
1256