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 ColorCorrect plugin.
21  */
22 
23 #include <cmath>
24 #include <algorithm>
25 #include <cfloat> // DBL_MAX
26 
27 #include "ofxsProcessing.H"
28 #include "ofxsMaskMix.h"
29 #include "ofxsCoords.h"
30 #include "ofxsLut.h"
31 #include "ofxsMacros.h"
32 #include "ofxsThreadSuite.h"
33 
34 using namespace OFX;
35 
36 OFXS_NAMESPACE_ANONYMOUS_ENTER
37 
38 #define kPluginName "ColorCorrectOFX"
39 #define kPluginGrouping "Color"
40 #define kPluginDescription "Adjusts the saturation, constrast, gamma, gain and offset of an image.\n" \
41     "The ranges of the shadows, midtones and highlights are controlled by the curves " \
42     "in the \"Ranges\" tab.\n" \
43     "The Contrast adjustment works using the formula: Output = (Input/0.18)^Contrast*0.18.\n" \
44     "\n" \
45     "See also:\n" \
46     "- http://opticalenquiry.com/nuke/index.php?title=ColorCorrect\n" \
47     "- https://compositormathematic.wordpress.com/2013/07/06/gamma-contrast/"
48 
49 #define kPluginIdentifier "net.sf.openfx.ColorCorrectPlugin"
50 // History:
51 // version 1.0: initial version
52 // version 2.0: use kNatronOfxParamProcess* parameters
53 // version 2.1: add range params
54 #define kPluginVersionMajor 2 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
55 #define kPluginVersionMinor 1 // Increment this when you have fixed a bug or made it faster.
56 
57 #define kSupportsTiles 1
58 #define kSupportsMultiResolution 1
59 #define kSupportsRenderScale 1
60 #define kSupportsMultipleClipPARs false
61 #define kSupportsMultipleClipDepths false
62 #define kRenderThreadSafety eRenderFullySafe
63 
64 ////std strings because we need them in changedParam
65 static const std::string kGroupMaster = std::string("Master");
66 static const std::string kGroupShadows = std::string("Shadows");
67 static const std::string kGroupMidtones = std::string("Midtones");
68 static const std::string kGroupHighlights = std::string("Highlights");
69 static const std::string kParamEnable = std::string("Enable");
70 static const std::string kParamSaturation = std::string("Saturation");
71 static const std::string kParamContrast = std::string("Contrast");
72 static const std::string kParamGamma = std::string("Gamma");
73 static const std::string kParamGain = std::string("Gain");
74 static const std::string kParamOffset = std::string("Offset");
75 
76 #ifdef OFX_EXTENSIONS_NATRON
77 #define kParamProcessR kNatronOfxParamProcessR
78 #define kParamProcessRLabel kNatronOfxParamProcessRLabel
79 #define kParamProcessRHint kNatronOfxParamProcessRHint
80 #define kParamProcessG kNatronOfxParamProcessG
81 #define kParamProcessGLabel kNatronOfxParamProcessGLabel
82 #define kParamProcessGHint kNatronOfxParamProcessGHint
83 #define kParamProcessB kNatronOfxParamProcessB
84 #define kParamProcessBLabel kNatronOfxParamProcessBLabel
85 #define kParamProcessBHint kNatronOfxParamProcessBHint
86 #define kParamProcessA kNatronOfxParamProcessA
87 #define kParamProcessALabel kNatronOfxParamProcessALabel
88 #define kParamProcessAHint kNatronOfxParamProcessAHint
89 #else
90 #define kParamProcessR      "processR"
91 #define kParamProcessRLabel "R"
92 #define kParamProcessRHint  "Process red component."
93 #define kParamProcessG      "processG"
94 #define kParamProcessGLabel "G"
95 #define kParamProcessGHint  "Process green component."
96 #define kParamProcessB      "processB"
97 #define kParamProcessBLabel "B"
98 #define kParamProcessBHint  "Process blue component."
99 #define kParamProcessA      "processA"
100 #define kParamProcessALabel "A"
101 #define kParamProcessAHint  "Process alpha component."
102 #endif
103 
104 #define kParamRange "range"
105 #define kParamRangeLabel "Range"
106 #define kParamRangeHint "Expected range for input values. Within this range, a lookup table is used for faster computation."
107 
108 #define kParamColorCorrectToneRanges "toneRanges"
109 #define kParamColorCorrectToneRangesLabel "Tone Ranges"
110 #define kParamColorCorrectToneRangesHint "Tone ranges lookup table"
111 #define kParamColorCorrectToneRangesDim0 "Shadow"
112 #define kParamColorCorrectToneRangesDim1 "Highlight"
113 
114 #define kParamLuminanceMath "luminanceMath"
115 #define kParamLuminanceMathLabel "Luminance Math"
116 #define kParamLuminanceMathHint "Formula used to compute luminance from RGB values (used for saturation adjustments)."
117 #define kParamLuminanceMathOptionRec709 "Rec. 709", "Use Rec. 709 (0.2126r + 0.7152g + 0.0722b).", "rec709"
118 #define kParamLuminanceMathOptionRec2020 "Rec. 2020", "Use Rec. 2020 (0.2627r + 0.6780g + 0.0593b).", "rec2020"
119 #define kParamLuminanceMathOptionACESAP0 "ACES AP0", "Use ACES AP0 (0.3439664498r + 0.7281660966g + -0.0721325464b).", "acesap0"
120 #define kParamLuminanceMathOptionACESAP1 "ACES AP1", "Use ACES AP1 (0.2722287168r +  0.6740817658g +  0.0536895174b).", "acesap1"
121 #define kParamLuminanceMathOptionCcir601 "CCIR 601", "Use CCIR 601 (0.2989r + 0.5866g + 0.1145b).", "ccir601"
122 #define kParamLuminanceMathOptionAverage "Average", "Use average of r, g, b.", "average"
123 #define kParamLuminanceMathOptionMaximum "Max", "Use max or r, g, b.", "max"
124 
125 enum LuminanceMathEnum
126 {
127     eLuminanceMathRec709,
128     eLuminanceMathRec2020,
129     eLuminanceMathACESAP0,
130     eLuminanceMathACESAP1,
131     eLuminanceMathCcir601,
132     eLuminanceMathAverage,
133     eLuminanceMathMaximum,
134 };
135 
136 #define kParamClampBlack "clampBlack"
137 #define kParamClampBlackLabel "Clamp Black"
138 #define kParamClampBlackHint "All colors below 0 on output are set to 0."
139 
140 #define kParamClampWhite "clampWhite"
141 #define kParamClampWhiteLabel "Clamp White"
142 #define kParamClampWhiteHint "All colors above 1 on output are set to 1."
143 
144 #define kParamPremultChanged "premultChanged"
145 
146 struct ColorControlValues
147 {
148     double r;
149     double g;
150     double b;
151     double a;
152 
ColorControlValuesColorControlValues153     ColorControlValues() : r(0.), g(0.), b(0.), a(0.) {}
154 
getValueFromColorControlValues155     void getValueFrom(double time,
156                       RGBAParam* p)
157     {
158         p->getValueAtTime(time, r, g, b, a);
159     }
160 
setColorControlValues161     void set(double r_,
162              double g_,
163              double b_,
164              double a_)
165     {
166         r = r_;
167         g = g_;
168         b = b_;
169         a = a_;
170     }
171 
setColorControlValues172     void set(double v)
173     {
174         r = g = b = a = v;
175     }
176 };
177 
178 struct ColorControlGroup
179 {
180     ColorControlValues saturation;
181     ColorControlValues contrast;
182     ColorControlValues gamma;
183     ColorControlValues gain;
184     ColorControlValues offset;
185 };
186 
187 template<typename T>
188 T
luminance(T r,T g,T b,LuminanceMathEnum luminanceMath)189 luminance(T r,
190           T g,
191           T b,
192           LuminanceMathEnum luminanceMath)
193 {
194     switch (luminanceMath) {
195     case eLuminanceMathRec709:
196     default:
197 
198         return Color::rgb709_to_y(r, g, b);
199 
200     case eLuminanceMathRec2020:     // https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2087-0-201510-I!!PDF-E.pdf
201 
202         return Color::rgb2020_to_y(r, g, b);
203     case eLuminanceMathACESAP0:     // https://en.wikipedia.org/wiki/Academy_Color_Encoding_System#Converting_ACES_RGB_values_to_CIE_XYZ_values
204 
205         return Color::rgbACESAP0_to_y(r, g, b);
206     case eLuminanceMathACESAP1:     // https://en.wikipedia.org/wiki/Academy_Color_Encoding_System#Converting_ACES_RGB_values_to_CIE_XYZ_values
207 
208         return Color::rgbACESAP1_to_y(r, g, b);
209     case eLuminanceMathCcir601:
210 
211         return (T)(0.2989f * r + 0.5866f * g + 0.1145f * b);
212     case eLuminanceMathAverage:
213 
214         return (r + g + b) / 3;
215     case eLuminanceMathMaximum:
216 
217         return std::max(std::max(r, g), b);
218     }
219 }
220 
221 template<bool processR, bool processG, bool processB, bool processA>
222 struct RGBAPixel
223 {
224     double r, g, b, a;
225     LuminanceMathEnum luminanceMath;
226 
RGBAPixelRGBAPixel227     RGBAPixel(double r_,
228               double g_,
229               double b_,
230               double a_,
231               LuminanceMathEnum luminanceMath_)
232         : r(r_)
233         , g(g_)
234         , b(b_)
235         , a(a_)
236         , luminanceMath(luminanceMath_)
237     {
238     }
239 
applySMHRGBAPixel240     void applySMH(const ColorControlGroup& sValues,
241                   double s_scale,
242                   const ColorControlGroup& mValues,
243                   double m_scale,
244                   const ColorControlGroup& hValues,
245                   double h_scale,
246                   const ColorControlGroup& masterValues)
247     {
248         RGBAPixel s = *this;
249         RGBAPixel m = *this;
250         RGBAPixel h = *this;
251 
252         s.applyGroup(sValues);
253         m.applyGroup(mValues);
254         h.applyGroup(hValues);
255 
256         if (processR) {
257             r = s.r * s_scale + m.r * m_scale + h.r * h_scale;
258         }
259         if (processG) {
260             g = s.g * s_scale + m.g * m_scale + h.g * h_scale;
261         }
262         if (processB) {
263             b = s.b * s_scale + m.b * m_scale + h.b * h_scale;
264         }
265         if (processA) {
266             a = s.a * s_scale + m.a * m_scale + h.a * h_scale;
267         }
268         applyGroup(masterValues);
269     }
270 
271 private:
applySaturationRGBAPixel272     void applySaturation(const ColorControlValues &c)
273     {
274         if (!(processR && c.r != 1.) &&
275             !(processG && c.g != 1.) &&
276             !(processB && c.b != 1.)) {
277 
278             return;
279         }
280         double l = luminance(r, g, b, luminanceMath);
281         if (processR && c.r != 1.) {
282             r = (1.f - c.r) * l + c.r * r;
283         }
284         if (processG && c.g != 1.) {
285             g = (1.f - c.g) * l + c.g * g;
286         }
287         if (processB && c.b != 1.) {
288             b = (1.f - c.b) * l + c.b * b;
289         }
290     }
291 
applyContrastRGBAPixel292     void applyContrast(const ColorControlValues &c)
293     {
294         // See https://compositormathematic.wordpress.com/2013/07/06/gamma-contrast/
295         // 0.18 is the value that a (maybe) correctly exposed grey card is in sRGB
296         // colour space. A grey card is a piece of card who’s surface is specially
297         // designed to reflect 18% of the light that hits it. It’s used in
298         // photography alongside a light meter to judge the correct exposure of a
299         // scene. The argument is that for some reason 18% is the value of middle
300         // grey, and all fingers seem to point to a photographer named Ansel
301         // Adams who somehow convinced the people at Kodak of this. You can read
302         // about it here: http://bythom.com/graycards.htm. People in the know say
303         // that this value of 18% is about 1/2 a stop wrong, and it should be more
304         // like 12%. It would also seem that the people making grey cards aren’t
305         // talking to the people making light meters.
306 
307         if (processR && (r > 0) && c.r != 1.) {
308             r = std::pow(r / 0.18, c.r) * 0.18;
309         }
310         if (processG && (g > 0) && c.g != 1.) {
311             g = std::pow(g / 0.18, c.g) * 0.18;
312         }
313         if (processB && (b > 0) && c.b != 1.) {
314             b = std::pow(b / 0.18, c.b) * 0.18;
315         }
316         if (processA && (a > 0) && c.a != 1.) {
317             a = std::pow(a / 0.18, c.a) * 0.18;
318         }
319     }
320 
applyGainRGBAPixel321     void applyGain(const ColorControlValues &c)
322     {
323         if (processR && c.r != 1.) {
324             r = r * c.r;
325         }
326         if (processG && c.g != 1.) {
327             g = g * c.g;
328         }
329         if (processB && c.b != 1.) {
330             b = b * c.b;
331         }
332         if (processA && c.a != 1.) {
333             a = a * c.a;
334         }
335     }
336 
applyGammaRGBAPixel337     void applyGamma(const ColorControlValues &c)
338     {
339         if ( processR && (r > 0) && c.r != 1. ) {
340             r = std::pow(r, 1. / c.r);
341         }
342         if ( processG && (g > 0) && c.g != 1. ) {
343             g = std::pow(g, 1. / c.g);
344         }
345         if ( processB && (b > 0) && c.b != 1. ) {
346             b = std::pow(b, 1. / c.b);
347         }
348         if ( processA && (a > 0) && c.a != 1. ) {
349             a = std::pow(a, 1. / c.a);
350         }
351     }
352 
applyOffsetRGBAPixel353     void applyOffset(const ColorControlValues &c)
354     {
355         if (processR && c.r != 0.) {
356             r = r + c.r;
357         }
358         if (processG && c.g != 0.) {
359             g = g + c.g;
360         }
361         if (processB && c.b != 0.) {
362             b = b + c.b;
363         }
364         if (processA && c.a != 0.) {
365             a = a + c.a;
366         }
367     }
368 
applyGroupRGBAPixel369     void applyGroup(const ColorControlGroup& group)
370     {
371         applySaturation(group.saturation);
372         applyContrast(group.contrast);
373         applyGamma(group.gamma);
374         applyGain(group.gain);
375         applyOffset(group.offset);
376     }
377 };
378 
379 class ColorCorrecterBase
380     : public ImageProcessor
381 {
382 protected:
383     const Image *_srcImg;
384     const Image *_maskImg;
385     bool _premult;
386     int _premultChannel;
387     bool _doMasking;
388     const bool _clampBlack;
389     const bool _clampWhite;
390     double _mix;
391     bool _maskInvert;
392     bool _processR, _processG, _processB, _processA;
393 
394 public:
ColorCorrecterBase(ImageEffect & instance,bool clampBlack,bool clampWhite,const RenderArguments &)395     ColorCorrecterBase(ImageEffect &instance,
396                        bool clampBlack,
397                        bool clampWhite,
398                        const RenderArguments & /*args*/)
399         : ImageProcessor(instance)
400         , _srcImg(NULL)
401         , _maskImg(NULL)
402         , _premult(false)
403         , _premultChannel(3)
404         , _doMasking(false)
405         , _clampBlack(clampBlack)
406         , _clampWhite(clampWhite)
407         , _mix(1.)
408         , _maskInvert(false)
409         , _processR(false)
410         , _processG(false)
411         , _processB(false)
412         , _processA(false)
413         , _luminanceMath(eLuminanceMathRec709)
414     {
415     }
416 
setSrcImg(const Image * v)417     void setSrcImg(const Image *v) {_srcImg = v; }
418 
setMaskImg(const Image * v,bool maskInvert)419     void setMaskImg(const Image *v,
420                     bool maskInvert) {_maskImg = v; _maskInvert = maskInvert; }
421 
doMasking(bool v)422     void doMasking(bool v) {_doMasking = v; }
423 
setColorControlValues(const ColorControlGroup & master,const ColorControlGroup & shadow,const ColorControlGroup & midtone,const ColorControlGroup & hightlights,LuminanceMathEnum luminanceMath,bool premult,int premultChannel,double mix,bool processR,bool processG,bool processB,bool processA)424     void setColorControlValues(const ColorControlGroup& master,
425                                const ColorControlGroup& shadow,
426                                const ColorControlGroup& midtone,
427                                const ColorControlGroup& hightlights,
428                                LuminanceMathEnum luminanceMath,
429                                bool premult,
430                                int premultChannel,
431                                double mix,
432                                bool processR,
433                                bool processG,
434                                bool processB,
435                                bool processA)
436     {
437         _masterValues = master;
438         _shadowValues = shadow;
439         _midtoneValues = midtone;
440         _highlightsValues = hightlights;
441         _luminanceMath = luminanceMath;
442         _premult = premult;
443         _premultChannel = premultChannel;
444         _mix = mix;
445         _processR = processR;
446         _processG = processG;
447         _processB = processB;
448         _processA = processA;
449     }
450 
451 
452 protected:
453     // clamp for integer PIX types
454     template<class PIX>
clamp(float value,int maxValue) const455     float clamp(float value,
456                 int maxValue) const
457     {
458         return std::max( 0.f, std::min( value, float(maxValue) ) );
459     }
460 
461     // clamp for integer PIX types
462     template<class PIX>
clamp(double value,int maxValue) const463     double clamp(double value,
464                  int maxValue) const
465     {
466         return std::max( 0., std::min( value, double(maxValue) ) );
467     }
468 
469 protected:
470     ColorControlGroup _masterValues;
471     ColorControlGroup _shadowValues;
472     ColorControlGroup _midtoneValues;
473     ColorControlGroup _highlightsValues;
474     LuminanceMathEnum _luminanceMath;
475 };
476 
477 // floats don't clamp except if _clampBlack or _clampWhite
478 template<>
479 float
clamp(float value,int maxValue) const480 ColorCorrecterBase::clamp<float>(float value,
481                                  int maxValue) const
482 {
483     assert(maxValue == 1.);
484     if ( _clampBlack && (value < 0.) ) {
485         value = 0.f;
486     } else if ( _clampWhite && (value > 1.0) ) {
487         value = 1.0f;
488     }
489 
490     return value;
491 }
492 
493 template<>
494 double
clamp(double value,int maxValue) const495 ColorCorrecterBase::clamp<float>(double value,
496                                  int maxValue) const
497 {
498     assert(maxValue == 1.);
499     if ( _clampBlack && (value < 0.) ) {
500         value = 0.f;
501     } else if ( _clampWhite && (value > 1.0) ) {
502         value = 1.0f;
503     }
504 
505     return value;
506 }
507 
508 // template to do the processing.
509 // nbValues is the number of values in the LUT minus 1. For integer types, it should be the same as
510 // maxValue
511 template <class PIX, int nComponents, int maxValue, int nbValues>
512 class ColorCorrecter
513     : public ColorCorrecterBase
514 {
515 public:
ColorCorrecter(ImageEffect & instance,const RenderArguments & args,ParametricParam * lookupTableParam,double rangeMin,double rangeMax,bool clampBlack,bool clampWhite)516     ColorCorrecter(ImageEffect &instance,
517                    const RenderArguments &args,
518                    ParametricParam  *lookupTableParam,
519                    double rangeMin,
520                    double rangeMax,
521                    bool clampBlack,
522                    bool clampWhite)
523         : ColorCorrecterBase(instance, clampBlack, clampWhite, args)
524         , _lookupTableParam(lookupTableParam)
525         , _rangeMin( std::min(rangeMin, rangeMax) )
526         , _rangeMax( std::max(rangeMin, rangeMax) )
527     {
528         _time = args.time;
529         // build the LUT
530         if (_rangeMin == _rangeMax) {
531             // avoid divisions by zero
532             _rangeMax = _rangeMin + 1.;
533         }
534         assert( (PIX)maxValue == maxValue );
535         // except for float, maxValue is the same as nbValues
536         assert( maxValue == 1 || (maxValue == nbValues) );
537         for (int curve = 0; curve < 2; ++curve) {
538             _lookupTable[curve].resize(nbValues + 1);
539             for (int position = 0; position <= nbValues; ++position) {
540                 // position to evaluate the param at
541                 double parametricPos = _rangeMin + (_rangeMax - _rangeMin) * double(position) / nbValues;
542 
543                 // evaluate the parametric param
544                 double value = lookupTableValue(curve, parametricPos);
545                 // set that in the lut
546                 _lookupTable[curve][position] = (float)clamp<PIX>(value, maxValue);
547             }
548         }
549     }
550 
multiThreadProcessImages(OfxRectI procWindow)551     void multiThreadProcessImages(OfxRectI procWindow)
552     {
553 #     ifndef __COVERITY__ // too many coverity[dead_error_line] errors
554         const bool r = _processR && (nComponents != 1);
555         const bool g = _processG && (nComponents >= 2);
556         const bool b = _processB && (nComponents >= 3);
557         const bool a = _processA && (nComponents == 1 || nComponents == 4);
558         if (r) {
559             if (g) {
560                 if (b) {
561                     if (a) {
562                         return process<true, true, true, true >(procWindow); // RGBA
563                     } else {
564                         return process<true, true, true, false>(procWindow); // RGBa
565                     }
566                 } else {
567                     if (a) {
568                         return process<true, true, false, true >(procWindow); // RGbA
569                     } else {
570                         return process<true, true, false, false>(procWindow); // RGba
571                     }
572                 }
573             } else {
574                 if (b) {
575                     if (a) {
576                         return process<true, false, true, true >(procWindow); // RgBA
577                     } else {
578                         return process<true, false, true, false>(procWindow); // RgBa
579                     }
580                 } else {
581                     if (a) {
582                         return process<true, false, false, true >(procWindow); // RgbA
583                     } else {
584                         return process<true, false, false, false>(procWindow); // Rgba
585                     }
586                 }
587             }
588         } else {
589             if (g) {
590                 if (b) {
591                     if (a) {
592                         return process<false, true, true, true >(procWindow); // rGBA
593                     } else {
594                         return process<false, true, true, false>(procWindow); // rGBa
595                     }
596                 } else {
597                     if (a) {
598                         return process<false, true, false, true >(procWindow); // rGbA
599                     } else {
600                         return process<false, true, false, false>(procWindow); // rGba
601                     }
602                 }
603             } else {
604                 if (b) {
605                     if (a) {
606                         return process<false, false, true, true >(procWindow); // rgBA
607                     } else {
608                         return process<false, false, true, false>(procWindow); // rgBa
609                     }
610                 } else {
611                     if (a) {
612                         return process<false, false, false, true >(procWindow); // rgbA
613                     } else {
614                         return process<false, false, false, false>(procWindow); // rgba
615                     }
616                 }
617             }
618         }
619 #     endif // ifndef __COVERITY__
620     } // multiThreadProcessImages
621 
622 private:
623 
624 
625     template<bool processR, bool processG, bool processB, bool processA>
process(OfxRectI procWindow)626     void process(OfxRectI procWindow)
627     {
628         assert( (!processR && !processG && !processB) || (nComponents == 3 || nComponents == 4) );
629         assert( !processA || (nComponents == 1 || nComponents == 4) );
630         assert(nComponents == 3 || nComponents == 4);
631         float unpPix[4];
632         float tmpPix[4];
633         for (int y = procWindow.y1; y < procWindow.y2; y++) {
634             if ( _effect.abort() ) {
635                 break;
636             }
637 
638             PIX *dstPix = (PIX *) _dstImg->getPixelAddress(procWindow.x1, y);
639             for (int x = procWindow.x1; x < procWindow.x2; x++) {
640                 const PIX *srcPix = (const PIX *)  (_srcImg ? _srcImg->getPixelAddress(x, y) : 0);
641                 ofxsUnPremult<PIX, nComponents, maxValue>(srcPix, unpPix, _premult, _premultChannel);
642                 double t_r = unpPix[0];
643                 double t_g = unpPix[1];
644                 double t_b = unpPix[2];
645                 double t_a = unpPix[3];
646                 colorTransform<processR, processG, processB, processA>(&t_r, &t_g, &t_b, &t_a);
647                 tmpPix[0] = (float)t_r;
648                 tmpPix[1] = (float)t_g;
649                 tmpPix[2] = (float)t_b;
650                 tmpPix[3] = (float)t_a;
651                 ofxsPremultMaskMixPix<PIX, nComponents, maxValue, true>(tmpPix, _premult, _premultChannel, x, y, srcPix, _doMasking, _maskImg, (float)_mix, _maskInvert, dstPix);
652                 // copy back original values from unprocessed channels
653                 if (nComponents == 1) {
654                     if (!processA) {
655                         dstPix[0] = srcPix ? srcPix[0] : PIX();
656                     }
657                 } else if ( (nComponents == 3) || (nComponents == 4) ) {
658                     if (!processR) {
659                         dstPix[0] = srcPix ? srcPix[0] : PIX();
660                     }
661                     if (!processG) {
662                         dstPix[1] = srcPix ? srcPix[1] : PIX();
663                     }
664                     if (!processB) {
665                         dstPix[2] = srcPix ? srcPix[2] : PIX();
666                     }
667                     if ( !processA && (nComponents == 4) ) {
668                         dstPix[3] = srcPix ? srcPix[3] : PIX();
669                     }
670                 }
671                 // increment the dst pixel
672                 dstPix += nComponents;
673             }
674         }
675     } // process
676 
677     template<bool processR, bool processG, bool processB, bool processA>
colorTransform(double * r,double * g,double * b,double * a)678     void colorTransform(double *r,
679                         double *g,
680                         double *b,
681                         double *a)
682     {
683         double l = luminance(*r, *g, *b, _luminanceMath);
684         double s_scale = interpolate(0, l);
685         double h_scale = interpolate(1, l);
686         double m_scale = 1.f - s_scale - h_scale;
687 
688         RGBAPixel<processR, processG, processB, processA> p(*r, *g, *b, *a, _luminanceMath);
689         p.applySMH(_shadowValues, s_scale,
690                    _midtoneValues, m_scale,
691                    _highlightsValues, h_scale,
692                    _masterValues);
693         if (processR) {
694             *r = clamp<float>(p.r, 1);
695         }
696         if (processG) {
697             *g = clamp<float>(p.g, 1);
698         }
699         if (processB) {
700             *b = clamp<float>(p.b, 1);
701         }
702         if (processA) {
703             *a = clamp<float>(p.a, 1);
704         }
705     }
706 
lookupTableValue(int curve,double parametricPos) const707     double lookupTableValue(int curve, double parametricPos) const
708     {
709         double value;
710         if (_lookupTableParam) {
711             value = _lookupTableParam->getValue(curve, _time, parametricPos);
712         } else if (curve == 0) {
713             if (parametricPos <= 0.) {
714                 value = 1.;
715             } else if (parametricPos < 0.09) {
716                 double x = parametricPos / 0.09;
717                 x = -2 * x * x * x + 3 * x * x; // cubic
718                 value = 1. - x;
719             } else {
720                 value = 0.;
721             }
722         } else {
723             assert(curve == 1);
724             if (parametricPos <= 0.5) {
725                 value = 0.;
726             } else if (parametricPos >= 1.) {
727                 value = 1.;
728             } else {
729                 double x = (parametricPos - 0.5) / 0.5;
730                 x = -2 * x * x * x + 3 * x * x; // cubic
731                 value = x;
732             }
733         }
734         return value;
735     }
736     // on input to interpolate, value should be normalized to the [0-1] range
interpolate(int component,float value) const737     float interpolate(int component,
738                       float value) const
739     {
740         if ( (value < _rangeMin) || (_rangeMax < value) ) {
741             // slow version
742             double ret = lookupTableValue(component, value);
743 
744             return clamp<float>(ret, 1);;
745         } else {
746             double x = (value - _rangeMin) / (_rangeMax - _rangeMin);
747             if (x <= 0.) {
748                 return _lookupTable[component][0];
749             } else if (x >= 1.) {
750                 return _lookupTable[component][nbValues];
751             }
752             int i = (int)(x * nbValues);
753             assert(0 <= i && i < nbValues);
754             i = std::max( 0, std::min(i, nbValues - 1) );
755             double alpha = std::max( 0., std::min(x * nbValues - i, 1.) );
756             float a = _lookupTable[component][i];
757             float b = _lookupTable[component][i + 1];
758 
759             return a * (1.f - alpha) + b * alpha;
760         }
761     }
762 
763     std::vector<float> _lookupTable[2];
764     ParametricParam*  _lookupTableParam;
765     double _time;
766     double _rangeMin;
767     double _rangeMax;
768 };
769 
770 struct ColorControlParamGroup
771 {
ColorControlParamGroupColorControlParamGroup772     ColorControlParamGroup()
773         : enable(NULL)
774         , saturation(NULL)
775         , contrast(NULL)
776         , gamma(NULL)
777         , gain(NULL)
778         , offset(NULL) {}
779 
780     BooleanParam* enable;
781     RGBAParam* saturation;
782     RGBAParam* contrast;
783     RGBAParam* gamma;
784     RGBAParam* gain;
785     RGBAParam* offset;
786 };
787 
788 ////////////////////////////////////////////////////////////////////////////////
789 /** @brief The plugin that does our work */
790 class ColorCorrectPlugin
791     : public ImageEffect
792 {
793 public:
794 
795     enum ColorCorrectGroupType
796     {
797         eGroupMaster = 0,
798         eGroupShadow,
799         eGroupMidtone,
800         eGroupHighlight
801     };
802 
803     /** @brief ctor */
ColorCorrectPlugin(OfxImageEffectHandle handle,bool supportsParametricParameter)804     ColorCorrectPlugin(OfxImageEffectHandle handle,
805                        bool supportsParametricParameter)
806         : ImageEffect(handle)
807         , _supportsParametricParameter(supportsParametricParameter)
808         , _dstClip(NULL)
809         , _srcClip(NULL)
810         , _maskClip(NULL)
811         , _processR(NULL)
812         , _processG(NULL)
813         , _processB(NULL)
814         , _processA(NULL)
815         , _range(NULL)
816         , _rangesParam(NULL)
817         , _luminanceMath(NULL)
818         , _clampBlack(NULL)
819         , _clampWhite(NULL)
820         , _premult(NULL)
821         , _premultChannel(NULL)
822         , _mix(NULL)
823         , _maskApply(NULL)
824         , _maskInvert(NULL)
825         , _premultChanged(NULL)
826     {
827         _dstClip = fetchClip(kOfxImageEffectOutputClipName);
828         assert( _dstClip && (!_dstClip->isConnected() || _dstClip->getPixelComponents() == ePixelComponentRGB ||
829                              _dstClip->getPixelComponents() == ePixelComponentRGBA) );
830         _srcClip = getContext() == eContextGenerator ? NULL : fetchClip(kOfxImageEffectSimpleSourceClipName);
831         assert( (!_srcClip && getContext() == eContextGenerator) ||
832                 ( _srcClip && (!_srcClip->isConnected() || _srcClip->getPixelComponents() ==  ePixelComponentRGB ||
833                                _srcClip->getPixelComponents() == ePixelComponentRGBA) ) );
834         _maskClip = fetchClip(getContext() == eContextPaint ? "Brush" : "Mask");
835         assert(!_maskClip || !_maskClip->isConnected() || _maskClip->getPixelComponents() == ePixelComponentAlpha);
836         fetchColorControlGroup(kGroupMaster, &_masterParamsGroup);
837         fetchColorControlGroup(kGroupShadows, &_shadowsParamsGroup);
838         fetchColorControlGroup(kGroupMidtones, &_midtonesParamsGroup);
839         fetchColorControlGroup(kGroupHighlights, &_highlightsParamsGroup);
840         _range = fetchDouble2DParam(kParamRange);
841         assert(_range);
842         if (_supportsParametricParameter) {
843             _rangesParam = fetchParametricParam(kParamColorCorrectToneRanges);
844             assert(_rangesParam);
845         }
846         _luminanceMath = fetchChoiceParam(kParamLuminanceMath);
847         assert(_luminanceMath);
848         _clampBlack = fetchBooleanParam(kParamClampBlack);
849         _clampWhite = fetchBooleanParam(kParamClampWhite);
850         assert(_clampBlack && _clampWhite);
851         _premult = fetchBooleanParam(kParamPremult);
852         _premultChannel = fetchChoiceParam(kParamPremultChannel);
853         assert(_premult && _premultChannel);
854         _mix = fetchDoubleParam(kParamMix);
855         _maskApply = ( ofxsMaskIsAlwaysConnected( OFX::getImageEffectHostDescription() ) && paramExists(kParamMaskApply) ) ? fetchBooleanParam(kParamMaskApply) : 0;
856         _maskInvert = fetchBooleanParam(kParamMaskInvert);
857         assert(_mix && _maskInvert);
858         _premultChanged = fetchBooleanParam(kParamPremultChanged);
859         assert(_premultChanged);
860 
861         _processR = fetchBooleanParam(kNatronOfxParamProcessR);
862         _processG = fetchBooleanParam(kNatronOfxParamProcessG);
863         _processB = fetchBooleanParam(kNatronOfxParamProcessB);
864         _processA = fetchBooleanParam(kNatronOfxParamProcessA);
865         assert(_processR && _processG && _processB && _processA);
866     }
867 
868 private:
869     /* Override the render */
870     virtual void render(const RenderArguments &args) OVERRIDE FINAL;
871 
872     /* set up and run a processor */
873     void setupAndProcess(ColorCorrecterBase &, const RenderArguments &args);
874 
875     virtual bool isIdentity(const IsIdentityArguments &args, Clip * &identityClip, double &identityTime, int& view, std::string& plane) OVERRIDE FINAL;
876 
877     /** @brief called when a clip has just been changed in some way (a rewire maybe) */
878     virtual void changedClip(const InstanceChangedArgs &args, const std::string &clipName) OVERRIDE FINAL;
879     virtual void changedParam(const InstanceChangedArgs &args, const std::string &paramName) OVERRIDE FINAL;
fetchColorControlGroup(const std::string & groupName,ColorControlParamGroup * group)880     void fetchColorControlGroup(const std::string& groupName,
881                                 ColorControlParamGroup* group)
882     {
883         assert(group);
884         group->enable = (groupName == kGroupMaster) ? 0 : fetchBooleanParam(groupName  + kParamEnable);
885         group->saturation = fetchRGBAParam(groupName  + kParamSaturation);
886         group->contrast = fetchRGBAParam(groupName +  kParamContrast);
887         group->gamma = fetchRGBAParam(groupName  + kParamGamma);
888         group->gain = fetchRGBAParam(groupName + kParamGain);
889         group->offset = fetchRGBAParam(groupName + kParamOffset);
890         assert(group->saturation && group->contrast && group->gamma && group->gain && group->offset);
891     }
892 
893     void getColorCorrectGroupValues(double time, ColorControlGroup* groupValues, ColorCorrectGroupType type);
894 
getGroup(ColorCorrectGroupType type)895     ColorControlParamGroup& getGroup(ColorCorrectGroupType type)
896     {
897         switch (type) {
898         case eGroupMaster:
899 
900             return _masterParamsGroup;
901         case eGroupShadow:
902 
903             return _shadowsParamsGroup;
904         case eGroupMidtone:
905 
906             return _midtonesParamsGroup;
907         case eGroupHighlight:
908 
909             return _highlightsParamsGroup;
910         default:
911             assert(false);
912             break;
913         }
914     }
915 
916 private:
917     bool _supportsParametricParameter;
918     // do not need to delete these, the ImageEffect is managing them for us
919     Clip *_dstClip;
920     Clip *_srcClip;
921     Clip *_maskClip;
922     ColorControlParamGroup _masterParamsGroup;
923     ColorControlParamGroup _shadowsParamsGroup;
924     ColorControlParamGroup _midtonesParamsGroup;
925     ColorControlParamGroup _highlightsParamsGroup;
926     BooleanParam* _processR;
927     BooleanParam* _processG;
928     BooleanParam* _processB;
929     BooleanParam* _processA;
930     Double2DParam* _range;
931     ParametricParam* _rangesParam;
932     ChoiceParam* _luminanceMath;
933     BooleanParam* _clampBlack;
934     BooleanParam* _clampWhite;
935     BooleanParam* _premult;
936     ChoiceParam* _premultChannel;
937     DoubleParam* _mix;
938     BooleanParam* _maskApply;
939     BooleanParam* _maskInvert;
940     BooleanParam* _premultChanged; // set to true the first time the user connects src
941 };
942 
943 
944 void
getColorCorrectGroupValues(double time,ColorControlGroup * groupValues,ColorCorrectGroupType type)945 ColorCorrectPlugin::getColorCorrectGroupValues(double time,
946                                                ColorControlGroup* groupValues,
947                                                ColorCorrectGroupType type)
948 {
949     ColorControlParamGroup& group = getGroup(type);
950     bool enable = true;
951 
952     if (group.enable) {
953         group.enable->getValueAtTime(time, enable);
954     }
955     if (enable) {
956         groupValues->saturation.getValueFrom(time, group.saturation);
957         groupValues->contrast.getValueFrom(time, group.contrast);
958         groupValues->gamma.getValueFrom(time, group.gamma);
959         groupValues->gain.getValueFrom(time, group.gain);
960         groupValues->offset.getValueFrom(time, group.offset);
961     } else {
962         groupValues->saturation.set(1.);
963         groupValues->contrast.set(1.);
964         groupValues->gamma.set(1.);
965         groupValues->gain.set(1.);
966         groupValues->offset.set(0.);
967     }
968 }
969 
970 ////////////////////////////////////////////////////////////////////////////////
971 /** @brief render for the filter */
972 
973 ////////////////////////////////////////////////////////////////////////////////
974 // basic plugin render function, just a skelington to instantiate templates from
975 
976 /* set up and run a processor */
977 void
setupAndProcess(ColorCorrecterBase & processor,const RenderArguments & args)978 ColorCorrectPlugin::setupAndProcess(ColorCorrecterBase &processor,
979                                     const RenderArguments &args)
980 {
981     const double time = args.time;
982 
983     auto_ptr<Image> dst( _dstClip->fetchImage(time) );
984 
985     if ( !dst.get() ) {
986         throwSuiteStatusException(kOfxStatFailed);
987     }
988     BitDepthEnum dstBitDepth    = dst->getPixelDepth();
989     PixelComponentEnum dstComponents  = dst->getPixelComponents();
990     if ( ( dstBitDepth != _dstClip->getPixelDepth() ) ||
991          ( dstComponents != _dstClip->getPixelComponents() ) ) {
992         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong depth or components");
993         throwSuiteStatusException(kOfxStatFailed);
994     }
995     if ( (dst->getRenderScale().x != args.renderScale.x) ||
996          ( dst->getRenderScale().y != args.renderScale.y) ||
997          ( ( dst->getField() != eFieldNone) /* for DaVinci Resolve */ && ( dst->getField() != args.fieldToRender) ) ) {
998         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
999         throwSuiteStatusException(kOfxStatFailed);
1000     }
1001     auto_ptr<const Image> src( ( _srcClip && _srcClip->isConnected() ) ?
1002                                     _srcClip->fetchImage(time) : 0 );
1003     if ( src.get() ) {
1004         if ( (src->getRenderScale().x != args.renderScale.x) ||
1005              ( src->getRenderScale().y != args.renderScale.y) ||
1006              ( ( src->getField() != eFieldNone) /* for DaVinci Resolve */ && ( src->getField() != args.fieldToRender) ) ) {
1007             setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
1008             throwSuiteStatusException(kOfxStatFailed);
1009         }
1010         BitDepthEnum srcBitDepth      = src->getPixelDepth();
1011         PixelComponentEnum srcComponents = src->getPixelComponents();
1012         if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
1013             throwSuiteStatusException(kOfxStatErrImageFormat);
1014         }
1015     }
1016     bool doMasking = ( ( !_maskApply || _maskApply->getValueAtTime(time) ) && _maskClip && _maskClip->isConnected() );
1017     auto_ptr<const Image> mask(doMasking ? _maskClip->fetchImage(time) : 0);
1018     if ( mask.get() ) {
1019         if ( (mask->getRenderScale().x != args.renderScale.x) ||
1020              ( mask->getRenderScale().y != args.renderScale.y) ||
1021              ( ( mask->getField() != eFieldNone) /* for DaVinci Resolve */ && ( mask->getField() != args.fieldToRender) ) ) {
1022             setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
1023             throwSuiteStatusException(kOfxStatFailed);
1024         }
1025     }
1026     if (doMasking) {
1027         bool maskInvert = _maskInvert->getValueAtTime(time);
1028         processor.doMasking(true);
1029         processor.setMaskImg(mask.get(), maskInvert);
1030     }
1031 
1032     processor.setDstImg( dst.get() );
1033     processor.setSrcImg( src.get() );
1034     processor.setRenderWindow(args.renderWindow);
1035 
1036     ColorControlGroup masterValues, shadowValues, midtoneValues, highlightValues;
1037     getColorCorrectGroupValues(time, &masterValues,    eGroupMaster);
1038     getColorCorrectGroupValues(time, &shadowValues,    eGroupShadow);
1039     getColorCorrectGroupValues(time, &midtoneValues,   eGroupMidtone);
1040     getColorCorrectGroupValues(time, &highlightValues, eGroupHighlight);
1041     LuminanceMathEnum luminanceMath = (LuminanceMathEnum)_luminanceMath->getValueAtTime(time);
1042     bool premult = _premult->getValueAtTime(time);
1043     int premultChannel = _premultChannel->getValueAtTime(time);
1044     double mix = _mix->getValueAtTime(time);
1045     bool processR = _processR->getValueAtTime(time);
1046     bool processG = _processG->getValueAtTime(time);
1047     bool processB = _processB->getValueAtTime(time);
1048     bool processA = _processA->getValueAtTime(time);
1049 
1050     processor.setColorControlValues(masterValues, shadowValues, midtoneValues, highlightValues, luminanceMath, premult, premultChannel, mix,
1051                                     processR, processG, processB, processA);
1052     processor.process();
1053 } // ColorCorrectPlugin::setupAndProcess
1054 
1055 // the overridden render function
1056 void
render(const RenderArguments & args)1057 ColorCorrectPlugin::render(const RenderArguments &args)
1058 {
1059     //std::cout << "render!\n";
1060     // instantiate the render code based on the pixel depth of the dst clip
1061     BitDepthEnum dstBitDepth    = _dstClip->getPixelDepth();
1062     PixelComponentEnum dstComponents  = _dstClip->getPixelComponents();
1063 
1064     assert( kSupportsMultipleClipPARs   || !_srcClip || _srcClip->getPixelAspectRatio() == _dstClip->getPixelAspectRatio() );
1065     assert( kSupportsMultipleClipDepths || !_srcClip || _srcClip->getPixelDepth()       == _dstClip->getPixelDepth() );
1066     assert(dstComponents == ePixelComponentRGB || dstComponents == ePixelComponentRGBA);
1067     double rangeMin, rangeMax;
1068     const double time = args.time;
1069 
1070     _range->getValueAtTime(time, rangeMin, rangeMax);
1071     bool clampBlack = _clampBlack->getValueAtTime(time);
1072     bool clampWhite = _clampWhite->getValueAtTime(time);
1073 
1074     if (dstComponents == ePixelComponentRGBA) {
1075         switch (dstBitDepth) {
1076         case eBitDepthUByte: {
1077             ColorCorrecter<unsigned char, 4, 255, 255> fred(*this, args, _rangesParam, rangeMin, rangeMax, clampBlack, clampWhite);
1078             setupAndProcess(fred, args);
1079             break;
1080         }
1081         case eBitDepthUShort: {
1082             ColorCorrecter<unsigned short, 4, 65535, 65535> fred(*this, args, _rangesParam, rangeMin, rangeMax, clampBlack, clampWhite);
1083             setupAndProcess(fred, args);
1084             break;
1085         }
1086         case eBitDepthFloat: {
1087             ColorCorrecter<float, 4, 1, 1023> fred(*this, args, _rangesParam, rangeMin, rangeMax, clampBlack, clampWhite);
1088             setupAndProcess(fred, args);
1089             break;
1090         }
1091         default:
1092             //std::cout << "depth usupported\n";
1093             throwSuiteStatusException(kOfxStatErrUnsupported);
1094         }
1095     } else {
1096         assert(dstComponents == ePixelComponentRGB);
1097         switch (dstBitDepth) {
1098         case eBitDepthUByte: {
1099             ColorCorrecter<unsigned char, 3, 255, 255> fred(*this, args, _rangesParam, rangeMin, rangeMax, clampBlack, clampWhite);
1100             setupAndProcess(fred, args);
1101             break;
1102         }
1103         case eBitDepthUShort: {
1104             ColorCorrecter<unsigned short, 3, 65535, 65535> fred(*this, args, _rangesParam, rangeMin, rangeMax, clampBlack, clampWhite);
1105             setupAndProcess(fred, args);
1106             break;
1107         }
1108         case eBitDepthFloat: {
1109             ColorCorrecter<float, 3, 1, 1023> fred(*this, args, _rangesParam, rangeMin, rangeMax, clampBlack, clampWhite);
1110             setupAndProcess(fred, args);
1111             break;
1112         }
1113         default:
1114             //std::cout << "components usupported\n";
1115             throwSuiteStatusException(kOfxStatErrUnsupported);
1116         }
1117     }
1118     //std::cout << "render! OK\n";
1119 } // ColorCorrectPlugin::render
1120 
1121 static bool
groupIsIdentity(const ColorControlGroup & group)1122 groupIsIdentity(const ColorControlGroup& group)
1123 {
1124     return (group.saturation.r == 1. &&
1125             group.saturation.g == 1. &&
1126             group.saturation.b == 1. &&
1127             group.saturation.a == 1. &&
1128             group.contrast.r == 1. &&
1129             group.contrast.g == 1. &&
1130             group.contrast.b == 1. &&
1131             group.contrast.a == 1. &&
1132             group.gamma.r == 1. &&
1133             group.gamma.g == 1. &&
1134             group.gamma.b == 1. &&
1135             group.gamma.a == 1. &&
1136             group.gain.r == 1. &&
1137             group.gain.g == 1. &&
1138             group.gain.b == 1. &&
1139             group.gain.a == 1. &&
1140             group.offset.r == 0. &&
1141             group.offset.g == 0. &&
1142             group.offset.b == 0. &&
1143             group.offset.a == 0.);
1144 }
1145 
1146 bool
isIdentity(const IsIdentityArguments & args,Clip * & identityClip,double &,int &,std::string &)1147 ColorCorrectPlugin::isIdentity(const IsIdentityArguments &args,
1148                                Clip * &identityClip,
1149                                double & /*identityTime*/
1150                             , int& /*view*/, std::string& /*plane*/)
1151 {
1152     //std::cout << "isIdentity!\n";
1153     double mix;
1154 
1155     _mix->getValueAtTime(args.time, mix);
1156 
1157     if (mix == 0. /*|| (!processR && !processG && !processB && !processA)*/) {
1158         identityClip = _srcClip;
1159 
1160         return true;
1161     }
1162 
1163     {
1164         bool processR;
1165         bool processG;
1166         bool processB;
1167         bool processA;
1168         _processR->getValueAtTime(args.time, processR);
1169         _processG->getValueAtTime(args.time, processG);
1170         _processB->getValueAtTime(args.time, processB);
1171         _processA->getValueAtTime(args.time, processA);
1172         if (!processR && !processG && !processB && !processA) {
1173             identityClip = _srcClip;
1174 
1175             return true;
1176         }
1177     }
1178 
1179     bool clampBlack, clampWhite;
1180     _clampBlack->getValueAtTime(args.time, clampBlack);
1181     _clampWhite->getValueAtTime(args.time, clampWhite);
1182     if (clampBlack || clampWhite) {
1183         return false;
1184     }
1185 
1186     ColorControlGroup masterValues, shadowValues, midtoneValues, highlightValues;
1187     getColorCorrectGroupValues(args.time, &masterValues,    eGroupMaster);
1188     getColorCorrectGroupValues(args.time, &shadowValues,    eGroupShadow);
1189     getColorCorrectGroupValues(args.time, &midtoneValues,   eGroupMidtone);
1190     getColorCorrectGroupValues(args.time, &highlightValues, eGroupHighlight);
1191     if ( groupIsIdentity(masterValues) &&
1192          groupIsIdentity(shadowValues) &&
1193          groupIsIdentity(midtoneValues) &&
1194          groupIsIdentity(highlightValues) ) {
1195         identityClip = _srcClip;
1196 
1197         //std::cout << "isIdentity! true\n";
1198         return true;
1199     }
1200 
1201     bool doMasking = ( ( !_maskApply || _maskApply->getValueAtTime(args.time) ) && _maskClip && _maskClip->isConnected() );
1202     if (doMasking) {
1203         bool maskInvert;
1204         _maskInvert->getValueAtTime(args.time, maskInvert);
1205         if (!maskInvert) {
1206             OfxRectI maskRoD;
1207             if (getImageEffectHostDescription()->supportsMultiResolution) {
1208                 // In Sony Catalyst Edit, clipGetRegionOfDefinition returns the RoD in pixels instead of canonical coordinates.
1209                 // In hosts that do not support multiResolution (e.g. Sony Catalyst Edit), all inputs have the same RoD anyway.
1210                 Coords::toPixelEnclosing(_maskClip->getRegionOfDefinition(args.time), args.renderScale, _maskClip->getPixelAspectRatio(), &maskRoD);
1211                 // effect is identity if the renderWindow doesn't intersect the mask RoD
1212                 if ( !Coords::rectIntersection<OfxRectI>(args.renderWindow, maskRoD, 0) ) {
1213                     identityClip = _srcClip;
1214 
1215                     return true;
1216                 }
1217             }
1218         }
1219     }
1220 
1221     //std::cout << "isIdentity! false\n";
1222     return false;
1223 } // ColorCorrectPlugin::isIdentity
1224 
1225 void
changedClip(const InstanceChangedArgs & args,const std::string & clipName)1226 ColorCorrectPlugin::changedClip(const InstanceChangedArgs &args,
1227                                 const std::string &clipName)
1228 {
1229     //std::cout << "changedClip!\n";
1230     if ( (clipName == kOfxImageEffectSimpleSourceClipName) &&
1231          _srcClip && _srcClip->isConnected() &&
1232          !_premultChanged->getValue() &&
1233          ( args.reason == eChangeUserEdit) ) {
1234         if (_srcClip->getPixelComponents() != ePixelComponentRGBA) {
1235             _premult->setValue(false);
1236         } else {
1237             switch ( _srcClip->getPreMultiplication() ) {
1238             case eImageOpaque:
1239                 _premult->setValue(false);
1240                 break;
1241             case eImagePreMultiplied:
1242                 _premult->setValue(true);
1243                 break;
1244             case eImageUnPreMultiplied:
1245                 _premult->setValue(false);
1246                 break;
1247             }
1248         }
1249     }
1250     //std::cout << "changedClip OK!\n";
1251 }
1252 
1253 void
changedParam(const InstanceChangedArgs & args,const std::string & paramName)1254 ColorCorrectPlugin::changedParam(const InstanceChangedArgs &args,
1255                                  const std::string &paramName)
1256 {
1257     const double time = args.time;
1258 
1259     if ( (paramName == kParamRange) && (args.reason == eChangeUserEdit) ) {
1260         double rmin, rmax;
1261         _range->getValueAtTime(time, rmin, rmax);
1262         if (rmax < rmin) {
1263             _range->setValue(rmax, rmin);
1264         }
1265     } else if ( (paramName == kParamPremult) && (args.reason == eChangeUserEdit) ) {
1266         _premultChanged->setValue(true);
1267     }
1268 }
1269 
1270 mDeclarePluginFactory(ColorCorrectPluginFactory, {ofxsThreadSuiteCheck();}, {});
1271 void
describe(ImageEffectDescriptor & desc)1272 ColorCorrectPluginFactory::describe(ImageEffectDescriptor &desc)
1273 {
1274     //std::cout << "describe!\n";
1275     // basic labels
1276     desc.setLabel(kPluginName);
1277     desc.setPluginGrouping(kPluginGrouping);
1278     desc.setPluginDescription(kPluginDescription);
1279 
1280     desc.addSupportedContext(eContextFilter);
1281     desc.addSupportedContext(eContextGeneral);
1282     desc.addSupportedContext(eContextPaint);
1283     desc.addSupportedBitDepth(eBitDepthUByte);
1284     desc.addSupportedBitDepth(eBitDepthUShort);
1285     desc.addSupportedBitDepth(eBitDepthFloat);
1286 
1287     // set a few flags
1288     desc.setSingleInstance(false);
1289     desc.setHostFrameThreading(false);
1290     desc.setSupportsMultiResolution(kSupportsMultiResolution);
1291     desc.setSupportsTiles(kSupportsTiles);
1292     desc.setTemporalClipAccess(false);
1293     desc.setRenderTwiceAlways(false);
1294     desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
1295     desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);
1296     desc.setRenderThreadSafety(kRenderThreadSafety);
1297     //std::cout << "describe! OK\n";
1298 
1299 #ifdef OFX_EXTENSIONS_NATRON
1300     desc.setChannelSelector(ePixelComponentNone); // we have our own channel selector
1301 #endif
1302 }
1303 
1304 static void
defineRGBAScaleParam(ImageEffectDescriptor & desc,const std::string & name,const std::string & label,const std::string & hint,GroupParamDescriptor * parent,PageParamDescriptor * page,double def,double min,double max)1305 defineRGBAScaleParam(ImageEffectDescriptor &desc,
1306                      const std::string &name,
1307                      const std::string &label,
1308                      const std::string &hint,
1309                      GroupParamDescriptor *parent,
1310                      PageParamDescriptor* page,
1311                      double def,
1312                      double min,
1313                      double max)
1314 {
1315     RGBAParamDescriptor *param = desc.defineRGBAParam(name);
1316 
1317     param->setLabel(label);
1318     param->setHint(hint);
1319     param->setDefault(def, def, def, def);
1320     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)
1321     param->setDisplayRange(min, min, min, min, max, max, max, max);
1322     if (parent) {
1323         param->setParent(*parent);
1324     }
1325     if (page) {
1326         page->addChild(*param);
1327     }
1328 }
1329 
1330 static void
defineColorGroup(const std::string & groupName,const std::string & hint,PageParamDescriptor * page,ImageEffectDescriptor & desc,bool open)1331 defineColorGroup(const std::string& groupName,
1332                  const std::string& hint,
1333                  PageParamDescriptor* page,
1334                  ImageEffectDescriptor &desc,
1335                  bool open)
1336 {
1337     GroupParamDescriptor* group = desc.defineGroupParam(groupName);
1338 
1339     if (group) {
1340         group->setLabel(groupName);
1341         group->setHint(hint);
1342         group->setOpen(open);
1343         if (page) {
1344             page->addChild(*group);
1345         }
1346     }
1347 
1348     if (groupName != kGroupMaster) {
1349         BooleanParamDescriptor *param = desc.defineBooleanParam(groupName + kParamEnable);
1350         param->setLabel(kParamEnable);
1351         param->setHint("When checked, " + groupName + " correction is enabled.");
1352         param->setDefault(true);
1353         if (group) {
1354             param->setParent(*group);
1355         }
1356         if (page) {
1357             page->addChild(*param);
1358         }
1359     }
1360     defineRGBAScaleParam(desc, groupName + kParamSaturation, kParamSaturation, hint, group, page, 1, 0, 4);
1361     defineRGBAScaleParam(desc, groupName + kParamContrast,   kParamContrast,   hint, group, page, 1, 0, 4);
1362     defineRGBAScaleParam(desc, groupName + kParamGamma,      kParamGamma,      hint, group, page, 1, 0.2, 5);
1363     defineRGBAScaleParam(desc, groupName + kParamGain,       kParamGain,       hint, group, page, 1, 0, 4);
1364     defineRGBAScaleParam(desc, groupName + kParamOffset,     kParamOffset,     hint, group, page, 0, -1, 1);
1365 }
1366 
1367 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)1368 ColorCorrectPluginFactory::describeInContext(ImageEffectDescriptor &desc,
1369                                              ContextEnum context)
1370 {
1371     //std::cout << "describeInContext!\n";
1372     // Source clip only in the filter context
1373     // create the mandated source clip
1374     ClipDescriptor *srcClip = desc.defineClip(kOfxImageEffectSimpleSourceClipName);
1375 
1376     srcClip->addSupportedComponent(ePixelComponentRGBA);
1377     srcClip->addSupportedComponent(ePixelComponentRGB);
1378     srcClip->setTemporalClipAccess(false);
1379     srcClip->setSupportsTiles(kSupportsTiles);
1380     srcClip->setIsMask(false);
1381 
1382     // create the mandated output clip
1383     ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
1384     dstClip->addSupportedComponent(ePixelComponentRGBA);
1385     dstClip->addSupportedComponent(ePixelComponentRGB);
1386     dstClip->setSupportsTiles(kSupportsTiles);
1387 
1388     ClipDescriptor *maskClip = (context == eContextPaint) ? desc.defineClip("Brush") : desc.defineClip("Mask");
1389     maskClip->addSupportedComponent(ePixelComponentAlpha);
1390     maskClip->setTemporalClipAccess(false);
1391     if (context != eContextPaint) {
1392         maskClip->setOptional(true);
1393     }
1394     maskClip->setSupportsTiles(kSupportsTiles);
1395     maskClip->setIsMask(true);
1396 
1397     // make some pages and to things in
1398     PageParamDescriptor *page = desc.definePageParam("Controls");
1399 
1400     {
1401         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamProcessR);
1402         param->setLabel(kParamProcessRLabel);
1403         param->setHint(kParamProcessRHint);
1404         param->setDefault(true);
1405         param->setLayoutHint(eLayoutHintNoNewLine, 1);
1406         if (page) {
1407             page->addChild(*param);
1408         }
1409     }
1410     {
1411         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamProcessG);
1412         param->setLabel(kParamProcessGLabel);
1413         param->setHint(kParamProcessGHint);
1414         param->setDefault(true);
1415         param->setLayoutHint(eLayoutHintNoNewLine, 1);
1416         if (page) {
1417             page->addChild(*param);
1418         }
1419     }
1420     {
1421         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamProcessB);
1422         param->setLabel(kParamProcessBLabel);
1423         param->setHint(kParamProcessBHint);
1424         param->setDefault(true);
1425         param->setLayoutHint(eLayoutHintNoNewLine, 1);
1426         if (page) {
1427             page->addChild(*param);
1428         }
1429     }
1430     {
1431         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamProcessA);
1432         param->setLabel(kParamProcessALabel);
1433         param->setHint(kParamProcessAHint);
1434         param->setDefault(false);
1435         if (page) {
1436             page->addChild(*param);
1437         }
1438     }
1439 
1440     defineColorGroup(kGroupMaster, "", page, desc, true);
1441     defineColorGroup(kGroupShadows, "", page, desc, false);
1442     defineColorGroup(kGroupMidtones, "", page, desc, false);
1443     defineColorGroup(kGroupHighlights, "", page, desc, false);
1444 
1445     {
1446         PageParamDescriptor* ranges = desc.definePageParam("Ranges");
1447         {
1448             Double2DParamDescriptor *param = desc.defineDouble2DParam(kParamRange);
1449             param->setLabel(kParamRangeLabel);
1450             param->setDimensionLabels("min", "max");
1451             param->setHint(kParamRangeHint);
1452             param->setDefault(0., 1.);
1453             param->setDoubleType(eDoubleTypePlain);
1454             param->setRange(-DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
1455             param->setDisplayRange(0., 0., 1., 1.);
1456             param->setUseHostNativeOverlayHandle(false);
1457             param->setAnimates(true);
1458             if (ranges) {
1459                 ranges->addChild(*param);
1460             }
1461         }
1462         const ImageEffectHostDescription &gHostDescription = *getImageEffectHostDescription();
1463         const bool supportsParametricParameter = ( gHostDescription.supportsParametricParameter &&
1464                                                   !(gHostDescription.hostName == "uk.co.thefoundry.nuke" &&
1465                                                     8 <= gHostDescription.versionMajor && gHostDescription.versionMajor <= 10) );  // Nuke 8-10 are known to *not* support Parametric
1466         if (supportsParametricParameter) {
1467             ParametricParamDescriptor* param = desc.defineParametricParam(kParamColorCorrectToneRanges);
1468             assert(param);
1469             param->setLabel(kParamColorCorrectToneRangesLabel);
1470             param->setHint(kParamColorCorrectToneRangesHint);
1471 
1472             // define it as two dimensional
1473             param->setDimension(2);
1474 
1475             param->setDimensionLabel(kParamColorCorrectToneRangesDim0, 0);
1476             param->setDimensionLabel(kParamColorCorrectToneRangesDim1, 1);
1477 
1478             // set the UI colour for each dimension
1479             const OfxRGBColourD shadow   = {0.6, 0.4, 0.6};
1480             const OfxRGBColourD highlight  =  {0.8, 0.7, 0.6};
1481             param->setUIColour( 0, shadow );
1482             param->setUIColour( 1, highlight );
1483 
1484             // set the min/max parametric range to 0..1
1485             param->setRange(0.0, 1.0);
1486             // set the default Y range to 0..1 for all dimensions
1487             param->setDimensionDisplayRange(0., 1., 0);
1488             param->setDimensionDisplayRange(0., 1., 1);
1489 
1490             param->addControlPoint(0, // curve to set
1491                                    0.0,         // time, ignored in this case, as we are not adding a key
1492                                    0.0,         // parametric position, zero
1493                                    1.0,         // value to be, 0
1494                                    false);         // don't add a key
1495             param->addControlPoint(0, 0.0, 0.09, 0.0, false);
1496 
1497             param->addControlPoint(1, 0.0, 0.5, 0.0, false);
1498             param->addControlPoint(1, 0.0, 1.0, 1.0, false);
1499             if (ranges) {
1500                 ranges->addChild(*param);
1501             }
1502         }
1503     }
1504     {
1505         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamLuminanceMath);
1506         param->setLabel(kParamLuminanceMathLabel);
1507         param->setHint(kParamLuminanceMathHint);
1508         assert(param->getNOptions() == eLuminanceMathRec709);
1509         param->appendOption(kParamLuminanceMathOptionRec709);
1510         assert(param->getNOptions() == eLuminanceMathRec2020);
1511         param->appendOption(kParamLuminanceMathOptionRec2020);
1512         assert(param->getNOptions() == eLuminanceMathACESAP0);
1513         param->appendOption(kParamLuminanceMathOptionACESAP0);
1514         assert(param->getNOptions() == eLuminanceMathACESAP1);
1515         param->appendOption(kParamLuminanceMathOptionACESAP1);
1516         assert(param->getNOptions() == eLuminanceMathCcir601);
1517         param->appendOption(kParamLuminanceMathOptionCcir601);
1518         assert(param->getNOptions() == eLuminanceMathAverage);
1519         param->appendOption(kParamLuminanceMathOptionAverage);
1520         assert(param->getNOptions() == eLuminanceMathMaximum);
1521         param->appendOption(kParamLuminanceMathOptionMaximum);
1522         if (page) {
1523             page->addChild(*param);
1524         }
1525     }
1526     {
1527         BooleanParamDescriptor *param = desc.defineBooleanParam(kParamClampBlack);
1528         param->setLabel(kParamClampBlackLabel);
1529         param->setHint(kParamClampBlackHint);
1530         param->setDefault(true);
1531         param->setAnimates(true);
1532         param->setLayoutHint(eLayoutHintNoNewLine, 0);
1533         if (page) {
1534             page->addChild(*param);
1535         }
1536     }
1537     {
1538         BooleanParamDescriptor *param = desc.defineBooleanParam(kParamClampWhite);
1539         param->setLabel(kParamClampWhiteLabel);
1540         param->setHint(kParamClampWhiteHint);
1541         param->setDefault(false);
1542         param->setAnimates(true);
1543         if (page) {
1544             page->addChild(*param);
1545         }
1546     }
1547 
1548     ofxsPremultDescribeParams(desc, page);
1549     ofxsMaskMixDescribeParams(desc, page);
1550     //std::cout << "describeInCotext! OK\n";
1551 
1552     {
1553         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamPremultChanged);
1554         param->setDefault(false);
1555         param->setIsSecretAndDisabled(true);
1556         param->setAnimates(false);
1557         param->setEvaluateOnChange(false);
1558         if (page) {
1559             page->addChild(*param);
1560         }
1561     }
1562 } // ColorCorrectPluginFactory::describeInContext
1563 
1564 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)1565 ColorCorrectPluginFactory::createInstance(OfxImageEffectHandle handle,
1566                                           ContextEnum /*context*/)
1567 {
1568     const ImageEffectHostDescription &gHostDescription = *getImageEffectHostDescription();
1569     const bool supportsParametricParameter = ( gHostDescription.supportsParametricParameter &&
1570                                                !(gHostDescription.hostName == "uk.co.thefoundry.nuke" &&
1571                                                  8 <= gHostDescription.versionMajor && gHostDescription.versionMajor <= 10) );  // Nuke 8-10 are known to *not* support Parametric
1572 
1573     return new ColorCorrectPlugin(handle, supportsParametricParameter);
1574 }
1575 
1576 static ColorCorrectPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
1577 mRegisterPluginFactoryInstance(p)
1578 
1579 OFXS_NAMESPACE_ANONYMOUS_EXIT
1580