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