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 &paramName) 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 &paramName)
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