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 * OFX Multiply plugin.
21 */
22
23 #include <cmath>
24 #include <cstring>
25 #include <cfloat> // DBL_MAX
26
27 #include "ofxsProcessing.H"
28 #include "ofxsMaskMix.h"
29 #include "ofxsCoords.h"
30 #include "ofxsMacros.h"
31 #ifdef OFX_EXTENSIONS_NATRON
32 #include "ofxNatron.h"
33 #endif
34 #include "ofxsThreadSuite.h"
35
36 using namespace OFX;
37
38 OFXS_NAMESPACE_ANONYMOUS_ENTER
39
40 #define kPluginName "MultiplyOFX"
41 #define kPluginGrouping "Color/Math"
42 #define kPluginDescription \
43 "Multiply the selected channels by a constant.\n" \
44 "See also: http://opticalenquiry.com/nuke/index.php?title=Multiply"
45
46 #define kPluginIdentifier "net.sf.openfx.MultiplyPlugin"
47 // History:
48 // version 1.0: initial version
49 // version 2.0: use kNatronOfxParamProcess* parameters
50 #define kPluginVersionMajor 2 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
51 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
52
53 #define kSupportsTiles 1
54 #define kSupportsMultiResolution 1
55 #define kSupportsRenderScale 1
56 #define kSupportsMultipleClipPARs false
57 #define kSupportsMultipleClipDepths false
58 #define kRenderThreadSafety eRenderFullySafe
59
60 #ifdef OFX_EXTENSIONS_NATRON
61 #define kParamProcessR kNatronOfxParamProcessR
62 #define kParamProcessRLabel kNatronOfxParamProcessRLabel
63 #define kParamProcessRHint kNatronOfxParamProcessRHint
64 #define kParamProcessG kNatronOfxParamProcessG
65 #define kParamProcessGLabel kNatronOfxParamProcessGLabel
66 #define kParamProcessGHint kNatronOfxParamProcessGHint
67 #define kParamProcessB kNatronOfxParamProcessB
68 #define kParamProcessBLabel kNatronOfxParamProcessBLabel
69 #define kParamProcessBHint kNatronOfxParamProcessBHint
70 #define kParamProcessA kNatronOfxParamProcessA
71 #define kParamProcessALabel kNatronOfxParamProcessALabel
72 #define kParamProcessAHint kNatronOfxParamProcessAHint
73 #else
74 #define kParamProcessR "processR"
75 #define kParamProcessRLabel "R"
76 #define kParamProcessRHint "Process red component."
77 #define kParamProcessG "processG"
78 #define kParamProcessGLabel "G"
79 #define kParamProcessGHint "Process green component."
80 #define kParamProcessB "processB"
81 #define kParamProcessBLabel "B"
82 #define kParamProcessBHint "Process blue component."
83 #define kParamProcessA "processA"
84 #define kParamProcessALabel "A"
85 #define kParamProcessAHint "Process alpha component."
86 #endif
87
88 #define kParamValueName "value"
89 #define kParamValueLabel "Value"
90 #define kParamValueHint "Constant to multiply with the selected channels."
91
92 #define kParamPremultChanged "premultChanged"
93
94 #ifdef OFX_EXTENSIONS_NATRON
95 #define OFX_COMPONENTS_OK(c) ((c)== ePixelComponentAlpha || (c) == ePixelComponentXY || (c) == ePixelComponentRGB || (c) == ePixelComponentRGBA)
96 #else
97 #define OFX_COMPONENTS_OK(c) ((c)== ePixelComponentAlpha || (c) == ePixelComponentRGB || (c) == ePixelComponentRGBA)
98 #endif
99
100
101 struct RGBAValues
102 {
103 double r, g, b, a;
RGBAValuesRGBAValues104 RGBAValues(double v) : r(v), g(v), b(v), a(v) {}
105
RGBAValuesRGBAValues106 RGBAValues() : r(0), g(0), b(0), a(0) {}
107 };
108
109 class MultiplyProcessorBase
110 : public ImageProcessor
111 {
112 protected:
113 const Image *_srcImg;
114 const Image *_maskImg;
115 bool _processR;
116 bool _processG;
117 bool _processB;
118 bool _processA;
119 RGBAValues _value;
120 bool _premult;
121 int _premultChannel;
122 bool _doMasking;
123 double _mix;
124 bool _maskInvert;
125
126 public:
127
MultiplyProcessorBase(ImageEffect & instance)128 MultiplyProcessorBase(ImageEffect &instance)
129 : ImageProcessor(instance)
130 , _srcImg(NULL)
131 , _maskImg(NULL)
132 , _processR(true)
133 , _processG(true)
134 , _processB(true)
135 , _processA(false)
136 , _value()
137 , _premult(false)
138 , _premultChannel(3)
139 , _doMasking(false)
140 , _mix(1.)
141 , _maskInvert(false)
142 {
143 }
144
setSrcImg(const Image * v)145 void setSrcImg(const Image *v) {_srcImg = v; }
146
setMaskImg(const Image * v,bool maskInvert)147 void setMaskImg(const Image *v,
148 bool maskInvert) { _maskImg = v; _maskInvert = maskInvert; }
149
doMasking(bool v)150 void doMasking(bool v) {_doMasking = v; }
151
setValues(bool processR,bool processG,bool processB,bool processA,const RGBAValues & value,bool premult,int premultChannel,double mix)152 void setValues(bool processR,
153 bool processG,
154 bool processB,
155 bool processA,
156 const RGBAValues& value,
157 bool premult,
158 int premultChannel,
159 double mix)
160 {
161 _processR = processR;
162 _processG = processG;
163 _processB = processB;
164 _processA = processA;
165 _value = value;
166 _premult = premult;
167 _premultChannel = premultChannel;
168 _mix = mix;
169 }
170
171 private:
172 };
173
174
175 template <class PIX, int nComponents, int maxValue>
176 class MultiplyProcessor
177 : public MultiplyProcessorBase
178 {
179 public:
MultiplyProcessor(ImageEffect & instance)180 MultiplyProcessor(ImageEffect &instance)
181 : MultiplyProcessorBase(instance)
182 {
183 }
184
185 private:
186
multiThreadProcessImages(OfxRectI procWindow)187 void multiThreadProcessImages(OfxRectI procWindow)
188 {
189 # ifndef __COVERITY__ // too many coverity[dead_error_line] errors
190 const bool r = _processR && (nComponents != 1);
191 const bool g = _processG && (nComponents >= 2);
192 const bool b = _processB && (nComponents >= 3);
193 const bool a = _processA && (nComponents == 1 || nComponents == 4);
194 if (r) {
195 if (g) {
196 if (b) {
197 if (a) {
198 return process<true, true, true, true >(procWindow); // RGBA
199 } else {
200 return process<true, true, true, false>(procWindow); // RGBa
201 }
202 } else {
203 if (a) {
204 return process<true, true, false, true >(procWindow); // RGbA
205 } else {
206 return process<true, true, false, false>(procWindow); // RGba
207 }
208 }
209 } else {
210 if (b) {
211 if (a) {
212 return process<true, false, true, true >(procWindow); // RgBA
213 } else {
214 return process<true, false, true, false>(procWindow); // RgBa
215 }
216 } else {
217 if (a) {
218 return process<true, false, false, true >(procWindow); // RgbA
219 } else {
220 return process<true, false, false, false>(procWindow); // Rgba
221 }
222 }
223 }
224 } else {
225 if (g) {
226 if (b) {
227 if (a) {
228 return process<false, true, true, true >(procWindow); // rGBA
229 } else {
230 return process<false, true, true, false>(procWindow); // rGBa
231 }
232 } else {
233 if (a) {
234 return process<false, true, false, true >(procWindow); // rGbA
235 } else {
236 return process<false, true, false, false>(procWindow); // rGba
237 }
238 }
239 } else {
240 if (b) {
241 if (a) {
242 return process<false, false, true, true >(procWindow); // rgBA
243 } else {
244 return process<false, false, true, false>(procWindow); // rgBa
245 }
246 } else {
247 if (a) {
248 return process<false, false, false, true >(procWindow); // rgbA
249 } else {
250 return process<false, false, false, false>(procWindow); // rgba
251 }
252 }
253 }
254 }
255 # endif // ifndef __COVERITY__
256 } // multiThreadProcessImages
257
258 template<bool processR, bool processG, bool processB, bool processA>
process(const OfxRectI & procWindow)259 void process(const OfxRectI& procWindow)
260 {
261 assert(nComponents == 1 || nComponents == 3 || nComponents == 4);
262 assert(_dstImg);
263 float unpPix[4];
264 float tmpPix[4];
265 for (int y = procWindow.y1; y < procWindow.y2; y++) {
266 if ( _effect.abort() ) {
267 break;
268 }
269
270 PIX *dstPix = (PIX *) _dstImg->getPixelAddress(procWindow.x1, y);
271
272 for (int x = procWindow.x1; x < procWindow.x2; x++) {
273 const PIX *srcPix = (const PIX *) (_srcImg ? _srcImg->getPixelAddress(x, y) : 0);
274 ofxsUnPremult<PIX, nComponents, maxValue>(srcPix, unpPix, _premult, _premultChannel);
275 for (int c = 0; c < 4; ++c) {
276 if ( processR && (c == 0) ) {
277 tmpPix[0] = unpPix[0] * (float)_value.r;
278 } else if ( processG && (c == 1) ) {
279 tmpPix[1] = unpPix[1] * (float)_value.g;
280 } else if ( processB && (c == 2) ) {
281 tmpPix[2] = unpPix[2] * (float)_value.b;
282 } else if ( processA && (c == 3) ) {
283 tmpPix[3] = unpPix[3] * (float)_value.a;
284 } else {
285 tmpPix[c] = unpPix[c];
286 }
287 }
288 ofxsPremultMaskMixPix<PIX, nComponents, maxValue, true>(tmpPix, _premult, _premultChannel, x, y, srcPix, _doMasking, _maskImg, (float)_mix, _maskInvert, dstPix);
289 // copy back original values from unprocessed channels
290 if (nComponents == 1) {
291 if (!processA) {
292 dstPix[0] = srcPix ? srcPix[0] : PIX();
293 }
294 } else if ( (nComponents == 3) || (nComponents == 4) ) {
295 if (!processR) {
296 dstPix[0] = srcPix ? srcPix[0] : PIX();
297 }
298 if (!processG) {
299 dstPix[1] = srcPix ? srcPix[1] : PIX();
300 }
301 if (!processB) {
302 dstPix[2] = srcPix ? srcPix[2] : PIX();
303 }
304 if ( !processA && (nComponents == 4) ) {
305 dstPix[3] = srcPix ? srcPix[3] : PIX();
306 }
307 }
308 // increment the dst pixel
309 dstPix += nComponents;
310 }
311 }
312 } // process
313 };
314
315
316 ////////////////////////////////////////////////////////////////////////////////
317 /** @brief The plugin that does our work */
318 class MultiplyPlugin
319 : public ImageEffect
320 {
321 public:
322 /** @brief ctor */
MultiplyPlugin(OfxImageEffectHandle handle)323 MultiplyPlugin(OfxImageEffectHandle handle)
324 : ImageEffect(handle)
325 , _dstClip(NULL)
326 , _srcClip(NULL)
327 , _maskClip(NULL)
328 , _processR(NULL)
329 , _processG(NULL)
330 , _processB(NULL)
331 , _processA(NULL)
332 , _value(NULL)
333 , _premult(NULL)
334 , _premultChannel(NULL)
335 , _mix(NULL)
336 , _maskApply(NULL)
337 , _maskInvert(NULL)
338 , _premultChanged(NULL)
339 {
340 _dstClip = fetchClip(kOfxImageEffectOutputClipName);
341 assert( _dstClip && (!_dstClip->isConnected() || OFX_COMPONENTS_OK(_dstClip->getPixelComponents())) );
342 _srcClip = getContext() == eContextGenerator ? NULL : fetchClip(kOfxImageEffectSimpleSourceClipName);
343 assert( (!_srcClip && getContext() == eContextGenerator) ||
344 ( _srcClip && (!_srcClip->isConnected() || OFX_COMPONENTS_OK(_srcClip->getPixelComponents())) ) );
345 _maskClip = fetchClip(getContext() == eContextPaint ? "Brush" : "Mask");
346 assert(!_maskClip || !_maskClip->isConnected() || _maskClip->getPixelComponents() == ePixelComponentAlpha);
347 _processR = fetchBooleanParam(kParamProcessR);
348 _processG = fetchBooleanParam(kParamProcessG);
349 _processB = fetchBooleanParam(kParamProcessB);
350 _processA = fetchBooleanParam(kParamProcessA);
351 assert(_processR && _processG && _processB && _processA);
352 _value = fetchRGBAParam(kParamValueName);
353 assert(_value);
354 _premult = fetchBooleanParam(kParamPremult);
355 _premultChannel = fetchChoiceParam(kParamPremultChannel);
356 assert(_premult && _premultChannel);
357 _mix = fetchDoubleParam(kParamMix);
358 _maskApply = ( ofxsMaskIsAlwaysConnected( OFX::getImageEffectHostDescription() ) && paramExists(kParamMaskApply) ) ? fetchBooleanParam(kParamMaskApply) : 0;
359 _maskInvert = fetchBooleanParam(kParamMaskInvert);
360 assert(_mix && _maskInvert);
361 _premultChanged = fetchBooleanParam(kParamPremultChanged);
362 assert(_premultChanged);
363 }
364
365 private:
366 /* Override the render */
367 virtual void render(const RenderArguments &args) OVERRIDE FINAL;
368
369 /* set up and run a processor */
370 void setupAndProcess(MultiplyProcessorBase &, const RenderArguments &args);
371
372 virtual bool isIdentity(const IsIdentityArguments &args, Clip * &identityClip, double &identityTime, int& view, std::string& plane) OVERRIDE FINAL;
373
374 /** @brief called when a clip has just been changed in some way (a rewire maybe) */
375 virtual void changedClip(const InstanceChangedArgs &args, const std::string &clipName) OVERRIDE FINAL;
376 virtual void changedParam(const InstanceChangedArgs &args, const std::string ¶mName) OVERRIDE FINAL;
377
378 private:
379 // do not need to delete these, the ImageEffect is managing them for us
380 Clip *_dstClip;
381 Clip *_srcClip;
382 Clip *_maskClip;
383 BooleanParam* _processR;
384 BooleanParam* _processG;
385 BooleanParam* _processB;
386 BooleanParam* _processA;
387 RGBAParam *_value;
388 BooleanParam* _premult;
389 ChoiceParam* _premultChannel;
390 DoubleParam* _mix;
391 BooleanParam* _maskApply;
392 BooleanParam* _maskInvert;
393 BooleanParam* _premultChanged; // set to true the first time the user connects src
394 };
395
396
397 ////////////////////////////////////////////////////////////////////////////////
398 /** @brief render for the filter */
399
400 ////////////////////////////////////////////////////////////////////////////////
401 // basic plugin render function, just a skelington to instantiate templates from
402
403 /* set up and run a processor */
404 void
setupAndProcess(MultiplyProcessorBase & processor,const RenderArguments & args)405 MultiplyPlugin::setupAndProcess(MultiplyProcessorBase &processor,
406 const RenderArguments &args)
407 {
408 auto_ptr<Image> dst( _dstClip->fetchImage(args.time) );
409
410 if ( !dst.get() ) {
411 throwSuiteStatusException(kOfxStatFailed);
412 }
413 BitDepthEnum dstBitDepth = dst->getPixelDepth();
414 PixelComponentEnum dstComponents = dst->getPixelComponents();
415 if ( ( dstBitDepth != _dstClip->getPixelDepth() ) ||
416 ( dstComponents != _dstClip->getPixelComponents() ) ) {
417 setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong depth or components");
418 throwSuiteStatusException(kOfxStatFailed);
419 }
420 if ( (dst->getRenderScale().x != args.renderScale.x) ||
421 ( dst->getRenderScale().y != args.renderScale.y) ||
422 ( ( dst->getField() != eFieldNone) /* for DaVinci Resolve */ && ( dst->getField() != args.fieldToRender) ) ) {
423 setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
424 throwSuiteStatusException(kOfxStatFailed);
425 }
426 auto_ptr<const Image> src( ( _srcClip && _srcClip->isConnected() ) ?
427 _srcClip->fetchImage(args.time) : 0 );
428 if ( src.get() ) {
429 if ( (src->getRenderScale().x != args.renderScale.x) ||
430 ( src->getRenderScale().y != args.renderScale.y) ||
431 ( ( src->getField() != eFieldNone) /* for DaVinci Resolve */ && ( src->getField() != args.fieldToRender) ) ) {
432 setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
433 throwSuiteStatusException(kOfxStatFailed);
434 }
435 BitDepthEnum srcBitDepth = src->getPixelDepth();
436 PixelComponentEnum srcComponents = src->getPixelComponents();
437 if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
438 throwSuiteStatusException(kOfxStatErrImageFormat);
439 }
440 }
441 bool doMasking = ( ( !_maskApply || _maskApply->getValueAtTime(args.time) ) && _maskClip && _maskClip->isConnected() );
442 auto_ptr<const Image> mask(doMasking ? _maskClip->fetchImage(args.time) : 0);
443 // do we do masking
444 if (doMasking) {
445 if ( mask.get() ) {
446 if ( (mask->getRenderScale().x != args.renderScale.x) ||
447 ( mask->getRenderScale().y != args.renderScale.y) ||
448 ( ( mask->getField() != eFieldNone) /* for DaVinci Resolve */ && ( mask->getField() != args.fieldToRender) ) ) {
449 setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
450 throwSuiteStatusException(kOfxStatFailed);
451 }
452 }
453 bool maskInvert;
454 _maskInvert->getValueAtTime(args.time, maskInvert);
455 processor.doMasking(true);
456 processor.setMaskImg(mask.get(), maskInvert);
457 }
458
459 // set the images
460 processor.setDstImg( dst.get() );
461 processor.setSrcImg( src.get() );
462 // set the render window
463 processor.setRenderWindow(args.renderWindow);
464
465 bool processR, processG, processB, processA;
466 _processR->getValueAtTime(args.time, processR);
467 _processG->getValueAtTime(args.time, processG);
468 _processB->getValueAtTime(args.time, processB);
469 _processA->getValueAtTime(args.time, processA);
470 RGBAValues value;
471 _value->getValueAtTime(args.time, value.r, value.g, value.b, value.a);
472 bool premult;
473 int premultChannel;
474 _premult->getValueAtTime(args.time, premult);
475 _premultChannel->getValueAtTime(args.time, premultChannel);
476 double mix;
477 _mix->getValueAtTime(args.time, mix);
478 processor.setValues(processR, processG, processB, processA,
479 value, premult, premultChannel, mix);
480
481 // Call the base class process member, this will call the derived templated process code
482 processor.process();
483 } // MultiplyPlugin::setupAndProcess
484
485 // the overridden render function
486 void
render(const RenderArguments & args)487 MultiplyPlugin::render(const RenderArguments &args)
488 {
489 // instantiate the render code based on the pixel depth of the dst clip
490 BitDepthEnum dstBitDepth = _dstClip->getPixelDepth();
491 PixelComponentEnum dstComponents = _dstClip->getPixelComponents();
492
493 assert( kSupportsMultipleClipPARs || !_srcClip || _srcClip->getPixelAspectRatio() == _dstClip->getPixelAspectRatio() );
494 assert( kSupportsMultipleClipDepths || !_srcClip || _srcClip->getPixelDepth() == _dstClip->getPixelDepth() );
495 assert(OFX_COMPONENTS_OK(dstComponents));
496 if (dstComponents == ePixelComponentRGBA) {
497 switch (dstBitDepth) {
498 case eBitDepthUByte: {
499 MultiplyProcessor<unsigned char, 4, 255> fred(*this);
500 setupAndProcess(fred, args);
501 break;
502 }
503 case eBitDepthUShort: {
504 MultiplyProcessor<unsigned short, 4, 65535> fred(*this);
505 setupAndProcess(fred, args);
506 break;
507 }
508 case eBitDepthFloat: {
509 MultiplyProcessor<float, 4, 1> fred(*this);
510 setupAndProcess(fred, args);
511 break;
512 }
513 default:
514 throwSuiteStatusException(kOfxStatErrUnsupported);
515 }
516 } else if (dstComponents == ePixelComponentAlpha) {
517 switch (dstBitDepth) {
518 case eBitDepthUByte: {
519 MultiplyProcessor<unsigned char, 1, 255> fred(*this);
520 setupAndProcess(fred, args);
521 break;
522 }
523 case eBitDepthUShort: {
524 MultiplyProcessor<unsigned short, 1, 65535> fred(*this);
525 setupAndProcess(fred, args);
526 break;
527 }
528 case eBitDepthFloat: {
529 MultiplyProcessor<float, 1, 1> fred(*this);
530 setupAndProcess(fred, args);
531 break;
532 }
533 default:
534 throwSuiteStatusException(kOfxStatErrUnsupported);
535 }
536 #ifdef OFX_EXTENSIONS_NATRON
537 } else if (dstComponents == ePixelComponentXY) {
538 switch (dstBitDepth) {
539 case eBitDepthUByte: {
540 MultiplyProcessor<unsigned char, 2, 255> fred(*this);
541 setupAndProcess(fred, args);
542 break;
543 }
544 case eBitDepthUShort: {
545 MultiplyProcessor<unsigned short, 2, 65535> fred(*this);
546 setupAndProcess(fred, args);
547 break;
548 }
549 case eBitDepthFloat: {
550 MultiplyProcessor<float, 2, 1> fred(*this);
551 setupAndProcess(fred, args);
552 break;
553 }
554 default:
555 throwSuiteStatusException(kOfxStatErrUnsupported);
556 }
557 #endif
558 } else {
559 assert(dstComponents == ePixelComponentRGB);
560 switch (dstBitDepth) {
561 case eBitDepthUByte: {
562 MultiplyProcessor<unsigned char, 3, 255> fred(*this);
563 setupAndProcess(fred, args);
564 break;
565 }
566 case eBitDepthUShort: {
567 MultiplyProcessor<unsigned short, 3, 65535> fred(*this);
568 setupAndProcess(fred, args);
569 break;
570 }
571 case eBitDepthFloat: {
572 MultiplyProcessor<float, 3, 1> fred(*this);
573 setupAndProcess(fred, args);
574 break;
575 }
576 default:
577 throwSuiteStatusException(kOfxStatErrUnsupported);
578 }
579 }
580 } // MultiplyPlugin::render
581
582 bool
isIdentity(const IsIdentityArguments & args,Clip * & identityClip,double &,int &,std::string &)583 MultiplyPlugin::isIdentity(const IsIdentityArguments &args,
584 Clip * &identityClip,
585 double & /*identityTime*/
586 , int& /*view*/, std::string& /*plane*/)
587 {
588 double mix;
589
590 _mix->getValueAtTime(args.time, mix);
591
592 if (mix == 0. /*|| (!processR && !processG && !processB && !processA)*/) {
593 identityClip = _srcClip;
594
595 return true;
596 }
597
598 bool processR, processG, processB, processA;
599 _processR->getValueAtTime(args.time, processR);
600 _processG->getValueAtTime(args.time, processG);
601 _processB->getValueAtTime(args.time, processB);
602 _processA->getValueAtTime(args.time, processA);
603 RGBAValues value;
604 _value->getValueAtTime(args.time, value.r, value.g, value.b, value.a);
605 if ( ( !processR || (value.r == 1.) ) &&
606 ( !processG || ( value.g == 1.) ) &&
607 ( !processB || ( value.b == 1.) ) &&
608 ( !processA || ( value.a == 1.) ) ) {
609 identityClip = _srcClip;
610
611 return true;
612 }
613
614 bool doMasking = ( ( !_maskApply || _maskApply->getValueAtTime(args.time) ) && _maskClip && _maskClip->isConnected() );
615 if (doMasking) {
616 bool maskInvert;
617 _maskInvert->getValueAtTime(args.time, maskInvert);
618 if (!maskInvert) {
619 OfxRectI maskRoD;
620 if (getImageEffectHostDescription()->supportsMultiResolution) {
621 // In Sony Catalyst Edit, clipGetRegionOfDefinition returns the RoD in pixels instead of canonical coordinates.
622 // In hosts that do not support multiResolution (e.g. Sony Catalyst Edit), all inputs have the same RoD anyway.
623 Coords::toPixelEnclosing(_maskClip->getRegionOfDefinition(args.time), args.renderScale, _maskClip->getPixelAspectRatio(), &maskRoD);
624 // effect is identity if the renderWindow doesn't intersect the mask RoD
625 if ( !Coords::rectIntersection<OfxRectI>(args.renderWindow, maskRoD, 0) ) {
626 identityClip = _srcClip;
627
628 return true;
629 }
630 }
631 }
632 }
633
634 return false;
635 } // MultiplyPlugin::isIdentity
636
637 void
changedClip(const InstanceChangedArgs & args,const std::string & clipName)638 MultiplyPlugin::changedClip(const InstanceChangedArgs &args,
639 const std::string &clipName)
640 {
641 if ( (clipName == kOfxImageEffectSimpleSourceClipName) &&
642 _srcClip && _srcClip->isConnected() &&
643 !_premultChanged->getValue() &&
644 ( args.reason == eChangeUserEdit) ) {
645 if (_srcClip->getPixelComponents() != ePixelComponentRGBA) {
646 _premult->setValue(false);
647 } else {
648 switch ( _srcClip->getPreMultiplication() ) {
649 case eImageOpaque:
650 _premult->setValue(false);
651 break;
652 case eImagePreMultiplied:
653 _premult->setValue(true);
654 break;
655 case eImageUnPreMultiplied:
656 _premult->setValue(false);
657 break;
658 }
659 }
660 }
661 }
662
663 void
changedParam(const InstanceChangedArgs & args,const std::string & paramName)664 MultiplyPlugin::changedParam(const InstanceChangedArgs &args,
665 const std::string ¶mName)
666 {
667 if ( (paramName == kParamPremult) && (args.reason == eChangeUserEdit) ) {
668 _premultChanged->setValue(true);
669 }
670 }
671
672 mDeclarePluginFactory(MultiplyPluginFactory, {ofxsThreadSuiteCheck();}, {});
673 void
describe(ImageEffectDescriptor & desc)674 MultiplyPluginFactory::describe(ImageEffectDescriptor &desc)
675 {
676 // basic labels
677 desc.setLabel(kPluginName);
678 desc.setPluginGrouping(kPluginGrouping);
679 desc.setPluginDescription(kPluginDescription);
680
681 desc.addSupportedContext(eContextFilter);
682 desc.addSupportedContext(eContextGeneral);
683 desc.addSupportedContext(eContextPaint);
684 desc.addSupportedBitDepth(eBitDepthUByte);
685 desc.addSupportedBitDepth(eBitDepthUShort);
686 desc.addSupportedBitDepth(eBitDepthFloat);
687
688 // set a few flags
689 desc.setSingleInstance(false);
690 desc.setHostFrameThreading(false);
691 desc.setSupportsMultiResolution(kSupportsMultiResolution);
692 desc.setSupportsTiles(kSupportsTiles);
693 desc.setTemporalClipAccess(false);
694 desc.setRenderTwiceAlways(false);
695 desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
696 desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);
697 desc.setRenderThreadSafety(kRenderThreadSafety);
698 #ifdef OFX_EXTENSIONS_NATRON
699 desc.setChannelSelector(ePixelComponentNone); // we have our own channel selector
700 #endif
701 }
702
703 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)704 MultiplyPluginFactory::describeInContext(ImageEffectDescriptor &desc,
705 ContextEnum context)
706 {
707 // Source clip only in the filter context
708 // create the mandated source clip
709 ClipDescriptor *srcClip = desc.defineClip(kOfxImageEffectSimpleSourceClipName);
710
711 srcClip->addSupportedComponent(ePixelComponentRGBA);
712 srcClip->addSupportedComponent(ePixelComponentRGB);
713 #ifdef OFX_EXTENSIONS_NATRON
714 srcClip->addSupportedComponent(ePixelComponentXY);
715 #endif
716 srcClip->addSupportedComponent(ePixelComponentAlpha);
717 srcClip->setTemporalClipAccess(false);
718 srcClip->setSupportsTiles(kSupportsTiles);
719 srcClip->setIsMask(false);
720
721 // create the mandated output clip
722 ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
723 dstClip->addSupportedComponent(ePixelComponentRGBA);
724 dstClip->addSupportedComponent(ePixelComponentRGB);
725 #ifdef OFX_EXTENSIONS_NATRON
726 dstClip->addSupportedComponent(ePixelComponentXY);
727 #endif
728 dstClip->addSupportedComponent(ePixelComponentAlpha);
729 dstClip->setSupportsTiles(kSupportsTiles);
730
731 ClipDescriptor *maskClip = (context == eContextPaint) ? desc.defineClip("Brush") : desc.defineClip("Mask");
732 maskClip->addSupportedComponent(ePixelComponentAlpha);
733 maskClip->setTemporalClipAccess(false);
734 if (context != eContextPaint) {
735 maskClip->setOptional(true);
736 }
737 maskClip->setSupportsTiles(kSupportsTiles);
738 maskClip->setIsMask(true);
739
740 // make some pages and to things in
741 PageParamDescriptor *page = desc.definePageParam("Controls");
742
743 {
744 BooleanParamDescriptor* param = desc.defineBooleanParam(kParamProcessR);
745 param->setLabel(kParamProcessRLabel);
746 param->setHint(kParamProcessRHint);
747 param->setDefault(true);
748 param->setLayoutHint(eLayoutHintNoNewLine, 1);
749 if (page) {
750 page->addChild(*param);
751 }
752 }
753 {
754 BooleanParamDescriptor* param = desc.defineBooleanParam(kParamProcessG);
755 param->setLabel(kParamProcessGLabel);
756 param->setHint(kParamProcessGHint);
757 param->setDefault(true);
758 param->setLayoutHint(eLayoutHintNoNewLine, 1);
759 if (page) {
760 page->addChild(*param);
761 }
762 }
763 {
764 BooleanParamDescriptor* param = desc.defineBooleanParam(kParamProcessB);
765 param->setLabel(kParamProcessBLabel);
766 param->setHint(kParamProcessBHint);
767 param->setDefault(true);
768 param->setLayoutHint(eLayoutHintNoNewLine, 1);
769 if (page) {
770 page->addChild(*param);
771 }
772 }
773 {
774 BooleanParamDescriptor* param = desc.defineBooleanParam(kParamProcessA);
775 param->setLabel(kParamProcessALabel);
776 param->setHint(kParamProcessAHint);
777 param->setDefault(false);
778 if (page) {
779 page->addChild(*param);
780 }
781 }
782
783 {
784 RGBAParamDescriptor *param = desc.defineRGBAParam(kParamValueName);
785 param->setLabel(kParamValueLabel);
786 param->setHint(kParamValueHint);
787 param->setDefault(1.0, 1.0, 1.0, 1.0);
788 param->setRange(-DBL_MAX, -DBL_MAX, -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX, DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
789 param->setDisplayRange(0, 0, 0, 0, 4, 4, 4, 4);
790 param->setAnimates(true); // can animate
791 if (page) {
792 page->addChild(*param);
793 }
794 }
795
796 ofxsPremultDescribeParams(desc, page);
797 ofxsMaskMixDescribeParams(desc, page);
798
799 {
800 BooleanParamDescriptor* param = desc.defineBooleanParam(kParamPremultChanged);
801 param->setDefault(false);
802 param->setIsSecretAndDisabled(true);
803 param->setAnimates(false);
804 param->setEvaluateOnChange(false);
805 if (page) {
806 page->addChild(*param);
807 }
808 }
809 } // MultiplyPluginFactory::describeInContext
810
811 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)812 MultiplyPluginFactory::createInstance(OfxImageEffectHandle handle,
813 ContextEnum /*context*/)
814 {
815 return new MultiplyPlugin(handle);
816 }
817
818 static MultiplyPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
819 mRegisterPluginFactoryInstance(p)
820
821 OFXS_NAMESPACE_ANONYMOUS_EXIT
822