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