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 HSVTool plugin.
21  */
22 
23 #define _USE_MATH_DEFINES
24 #include <cmath>
25 #include <cfloat> // DBL_MAX
26 #include <algorithm>
27 
28 #include "ofxsProcessing.H"
29 #include "ofxsMaskMix.h"
30 #include "ofxsCoords.h"
31 #include "ofxsLut.h"
32 #include "ofxsMacros.h"
33 #include "ofxsRectangleInteract.h"
34 #include "ofxsThreadSuite.h"
35 #include "ofxsMultiThread.h"
36 #ifdef OFX_USE_MULTITHREAD_MUTEX
37 namespace {
38 typedef MultiThread::Mutex Mutex;
39 typedef MultiThread::AutoMutex AutoMutex;
40 }
41 #else
42 // some OFX hosts do not have mutex handling in the MT-Suite (e.g. Sony Catalyst Edit)
43 // prefer using the fast mutex by Marcus Geelnard http://tinythreadpp.bitsnbites.eu/
44 #include "fast_mutex.h"
45 namespace {
46 typedef tthread::fast_mutex Mutex;
47 typedef OFX::MultiThread::AutoMutexT<tthread::fast_mutex> AutoMutex;
48 }
49 #endif
50 
51 using namespace OFX;
52 
53 OFXS_NAMESPACE_ANONYMOUS_ENTER
54 
55 #define kPluginName "HSVToolOFX"
56 #define kPluginGrouping "Color"
57 #define kPluginDescription \
58     "Adjust hue, saturation and brightness, or perform color replacement.\n" \
59     "\n" \
60     "Color replacement:\n" \
61     "Set the srcColor and dstColor parameters. The range of the replacement is determined by the three groups of parameters: Hue, Saturation and Brightness.\n" \
62     "\n" \
63     "Color adjust:\n" \
64     "Use the Rotation of the Hue parameter and the Adjustment of the Saturation and Lightness. " \
65     "The ranges and falloff parameters allow for more complex adjustments.\n" \
66     "\n" \
67     "Hue keyer:\n" \
68     "Set the outputAlpha parameter (the last one) to All (the default is Hue), and use a viewer to display the Alpha channel. " \
69     "First, set the Range parameter of the Hue parameter set and then work down the other Ranges parameters, tuning with the range Falloff and Adjustment parameters." \
70 
71 #define kPluginIdentifier "net.sf.openfx.HSVToolPlugin"
72 #define kPluginVersionMajor 1 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
73 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
74 
75 #define kSupportsTiles 1
76 #define kSupportsMultiResolution 1
77 #define kSupportsRenderScale 1
78 #define kSupportsMultipleClipPARs false
79 #define kSupportsMultipleClipDepths false
80 #define kRenderThreadSafety eRenderFullySafe
81 
82 
83 #define kGroupColorReplacement "colorReplacement"
84 #define kGroupColorReplacementLabel "Color Replacement"
85 #define kGroupColorReplacementHint "Easily replace a given color by another color by setting srcColor and dstColor. Set Src Color first, then Dst Color."
86 #define kParamSrcColor "srcColor"
87 #define kParamSrcColorLabel "Src Color"
88 #define kParamSrcColorHint "Source color for replacement. Changing this parameter sets the hue, saturation and brightness ranges for this color, and sets the fallofs to default values."
89 #define kParamDstColor "dstColor"
90 #define kParamDstColorLabel "Dst Color"
91 #define kParamDstColorHint "Destination color for replacement. Changing this parameter sets the hue rotation, and saturation and brightness adjustments. Should be set after Src Color."
92 
93 #define kParamEnableRectangle "enableRectangle"
94 #define kParamEnableRectangleLabel "Src Analysis Rectangle"
95 #define kParamEnableRectangleHint "Enable the rectangle interact for analysis of Src and Dst colors and ranges."
96 
97 #define kParamSetSrcFromRectangle "setSrcFromRectangle"
98 #define kParamSetSrcFromRectangleLabel "Set Src from Rectangle"
99 #define kParamSetSrcFromRectangleHint "Set the Src color and ranges and the adjustments from the colors of the source image within the selection rectangle and the Dst Color."
100 
101 #define kGroupHue "hue"
102 #define kGroupHueLabel "Hue"
103 #define kGroupHueHint "Hue modification settings."
104 #define kParamHueRange "hueRange"
105 #define kParamHueRangeLabel "Hue Range"
106 #define kParamHueRangeHint "Range of color hues that are modified (in degrees). Red is 0, green is 120, blue is 240. The affected hue range is the smallest interval. For example, if the range is (12, 348), then the selected range is red plus or minus 12 degrees. Exception: if the range width is exactly 360, then all hues are modified."
107 #define kParamHueRotation "hueRotation"
108 #define kParamHueRotationLabel "Hue Rotation"
109 #define kParamHueRotationHint "Rotation of color hues (in degrees) within the range."
110 #define kParamHueRotationGain "hueRotationGain"
111 #define kParamHueRotationGainLabel "Hue Rotation Gain"
112 #define kParamHueRotationGainHint "Factor to be applied to the rotation of color hues (in degrees) within the range. A value of 0 will set all values within range to a constant (computed at the center of the range), and a value of 1 will add hueRotation to all values within range."
113 #define kParamHueRangeRolloff "hueRangeRolloff"
114 #define kParamHueRangeRolloffLabel "Hue Range Rolloff"
115 #define kParamHueRangeRolloffHint "Interval (in degrees) around Hue Range, where hue rotation decreases progressively to zero."
116 
117 #define kGroupSaturation "saturation"
118 #define kGroupSaturationLabel "Saturation"
119 #define kGroupSaturationHint "Saturation modification settings."
120 #define kParamSaturationRange "saturationRange"
121 #define kParamSaturationRangeLabel "Saturation Range"
122 #define kParamSaturationRangeHint "Range of color saturations that are modified."
123 #define kParamSaturationAdjustment "saturationAdjustment"
124 #define kParamSaturationAdjustmentLabel "Saturation Adjustment"
125 #define kParamSaturationAdjustmentHint "Adjustment of color saturations within the range. Saturation is clamped to zero to avoid color inversions."
126 #define kParamSaturationAdjustmentGain "saturationAdjustmentGain"
127 #define kParamSaturationAdjustmentGainLabel "Saturation Adjustment Gain"
128 #define kParamSaturationAdjustmentGainHint "Factor to be applied to the saturation adjustment within the range. A value of 0 will set all values within range to a constant (computed at the center of the range), and a value of 1 will add saturationAdjustment to all values within range."
129 #define kParamSaturationRangeRolloff "saturationRangeRolloff"
130 #define kParamSaturationRangeRolloffLabel "Saturation Range Rolloff"
131 #define kParamSaturationRangeRolloffHint "Interval (in degrees) around Saturation Range, where saturation rotation decreases progressively to zero."
132 
133 #define kGroupBrightness "brightness"
134 #define kGroupBrightnessLabel "Brightness"
135 #define kGroupBrightnessHint "Brightness modification settings."
136 #define kParamBrightnessRange "brightnessRange"
137 #define kParamBrightnessRangeLabel "Brightness Range"
138 #define kParamBrightnessRangeHint "Range of color brightnesss that are modified."
139 #define kParamBrightnessAdjustment "brightnessAdjustment"
140 #define kParamBrightnessAdjustmentLabel "Brightness Adjustment"
141 #define kParamBrightnessAdjustmentHint "Adjustment of color brightnesss within the range."
142 #define kParamBrightnessAdjustmentGain "brightnessAdjustmentGain"
143 #define kParamBrightnessAdjustmentGainLabel "Brightness Adjustment Gain"
144 #define kParamBrightnessAdjustmentGainHint "Factor to be applied to the brightness adjustment within the range. A value of 0 will set all values within range to a constant (computed at the center of the range), and a value of 1 will add brightnessAdjustment to all values within range."
145 #define kParamBrightnessRangeRolloff "brightnessRangeRolloff"
146 #define kParamBrightnessRangeRolloffLabel "Brightness Range Rolloff"
147 #define kParamBrightnessRangeRolloffHint "Interval (in degrees) around Brightness Range, where brightness rotation decreases progressively to zero."
148 
149 #define kParamClampBlack "clampBlack"
150 #define kParamClampBlackLabel "Clamp Black"
151 #define kParamClampBlackHint "All colors below 0 on output are set to 0."
152 
153 #define kParamClampWhite "clampWhite"
154 #define kParamClampWhiteLabel "Clamp White"
155 #define kParamClampWhiteHint "All colors above 1 on output are set to 1."
156 
157 #define kParamOutputAlpha "outputAlpha"
158 #define kParamOutputAlphaLabel "Output Alpha"
159 #define kParamOutputAlphaHint "Output alpha channel. This can either be the source alpha, one of the coefficients for hue, saturation, brightness, or a combination of those. If it is not source alpha, the image on output are unpremultiplied, even if input is premultiplied."
160 #define kParamOutputAlphaOptionSource "Source", "Alpha channel is kept unmodified.", "source"
161 #define kParamOutputAlphaOptionHue "Hue", "Set Alpha to the Hue modification mask.", "hue"
162 #define kParamOutputAlphaOptionSaturation "Saturation", "Set Alpha to the Saturation modification mask.", "saturation"
163 #define kParamOutputAlphaOptionBrightness "Brightness", "Alpha is set to the Brighness mask.", "brightness"
164 #define kParamOutputAlphaOptionHueSaturation "min(Hue,Saturation)", "Alpha is set to min(Hue mask,Saturation mask)", "minhuesaturation"
165 #define kParamOutputAlphaOptionHueBrightness "min(Hue,Brightness)", "Alpha is set to min(Hue mask,Brightness mask)", "minhuebrightness"
166 #define kParamOutputAlphaOptionSaturationBrightness "min(Saturation,Brightness)", "Alpha is set to min(Saturation mask,Brightness mask)", "minsaturationbrightness"
167 #define kParamOutputAlphaOptionAll "min(all)", "Alpha is set to min(Hue mask,Saturation mask,Brightness mask)", "min"
168 
169 enum OutputAlphaEnum
170 {
171     eOutputAlphaSource,
172     eOutputAlphaHue,
173     eOutputAlphaSaturation,
174     eOutputAlphaBrightness,
175     eOutputAlphaHueSaturation,
176     eOutputAlphaHueBrightness,
177     eOutputAlphaSaturationBrightness,
178     eOutputAlphaAll,
179 };
180 
181 #define kParamPremultChanged "premultChanged"
182 
183 // Some hosts (e.g. Resolve) may not support normalized defaults (setDefaultCoordinateSystem(eCoordinatesNormalised))
184 #define kParamDefaultsNormalised "defaultsNormalised"
185 
186 // to compute the rolloff for a default distribution, we approximate the gaussian with a piecewise linear function
187 // f(0) = 1, f'(0) = 0
188 // f(sigma*0.5*sqrt(12)) = 1/2, f'(sigma*0.5*sqrt(12)) = g'(sigma) (g is exp(-x^2/(2*sigma^2)))
189 // f(inf) = 0, f'(inf) = 0
190 //#define GAUSSIAN_ROLLOFF 0.8243606354 // exp(1/2)/2
191 //#define GAUSSIAN_RANGE 1.7320508075 // 0.5*sqrt(12)
192 
193 // minimum S and V components to take hue into account (hue is too noisy below these values)
194 #define MIN_SATURATION 0.1
195 #define MIN_VALUE 0.1
196 
197 // default fraction of the min-max interval to use as rolloff after rectangle analysis
198 #define DEFAULT_RECTANGLE_ROLLOFF 0.5
199 
200 static bool gHostSupportsDefaultCoordinateSystem = true; // for kParamDefaultsNormalised
201 
202 /* algorithm:
203    - convert to HSV
204    - compute H, S, and V coefficients: 1 within range, dropping to 0 at range+-rolloff
205    - compute min of the three coeffs. coeff = min(hcoeff,scoeff,vcoeff)
206    - if global coeff is 0, don't change anything.
207    - else, adjust hue by hueRotation*coeff, etc.
208    - convert back to RGB
209 
210    - when setting srcColor: compute hueRange, satRange, valRange (as empty ranges), set rolloffs to (50,0.3,0.3)
211    - when setting dstColor: compute hueRotation, satAdjust and valAdjust
212  */
213 struct HSVToolValues
214 {
215     double hueRange[2];
216     double hueRangeWithRolloff[2];
217     double hueRotation;
218     double hueMean;
219     double hueRotationGain;
220     double hueRolloff;
221     double satRange[2];
222     double satAdjust;
223     double satAdjustGain;
224     double satRolloff;
225     double valRange[2];
226     double valAdjust;
227     double valAdjustGain;
228     double valRolloff;
HSVToolValuesHSVToolValues229     HSVToolValues()
230     {
231         hueRange[0] = hueRange[1] = 0.;
232         hueRangeWithRolloff[0] = hueRangeWithRolloff[1] = 0.;
233         hueRotation = 0.;
234         hueMean = 0.;
235         hueRotationGain = 1.;
236         hueRolloff = 0.;
237         satRange[0] = satRange[1] = 0.;
238         satAdjust = 0.;
239         satAdjustGain = 1.;
240         satRolloff = 0.;
241         valRange[0] = valRange[1] = 0.;
242         valAdjust = 0.;
243         valAdjustGain = 1.;
244         valRolloff = 0.;
245     }
246 };
247 
248 //
249 static inline
250 double
normalizeAngle(double a)251 normalizeAngle(double a)
252 {
253     int c = (int)std::floor(a / 360);
254 
255     a -= c * 360;
256     assert(a >= 0 && a <= 360);
257 
258     return a;
259 }
260 
261 static inline
262 double
normalizeAngleSigned(double a)263 normalizeAngleSigned(double a)
264 {
265     return normalizeAngle(a + 180.) - 180.;
266 }
267 
268 static inline
269 bool
angleWithinRange(double h,double h0,double h1)270 angleWithinRange(double h,
271                  double h0,
272                  double h1)
273 {
274     assert(0 <= h && h <= 360 && 0 <= h0 && h0 <= 360 && 0 <= h1 && h1 <= 360);
275 
276     return ( ( h1 < h0 && (h <= h1 || h0 <= h) ) ||
277              (h0 <= h && h <= h1) );
278 }
279 
280 // Exponentiation by squaring
281 // works with positive or negative integer exponents
282 template<typename T>
283 T
ipow(T base,int exp)284 ipow(T base,
285      int exp)
286 {
287     T result = T(1);
288 
289     if (exp >= 0) {
290         while (exp) {
291             if (exp & 1) {
292                 result *= base;
293             }
294             exp >>= 1;
295             base *= base;
296         }
297     } else {
298         exp = -exp;
299         while (exp) {
300             if (exp & 1) {
301                 result /= base;
302             }
303             exp >>= 1;
304             base *= base;
305         }
306     }
307 
308     return result;
309 }
310 
311 static double
ffloor(double val,int decimals)312 ffloor(double val,
313        int decimals)
314 {
315     int p = ipow(10, decimals);
316 
317     return std::floor(val * p) / p;
318 }
319 
320 static double
fround(double val,int decimals)321 fround(double val,
322        int decimals)
323 {
324     int p = ipow(10, decimals);
325 
326     return std::floor(val * p + 0.5) / p;
327 }
328 
329 static double
fceil(double val,int decimals)330 fceil(double val,
331       int decimals)
332 {
333     int p = ipow(10, decimals);
334 
335     return std::ceil(val * p) / p;
336 }
337 
338 // returns:
339 // - 0 if outside of [h0, h1]
340 // - 0 at h0
341 // - 1 at h1
342 // - linear from h0 to h1
343 static inline
344 double
angleCoeff01(double h,double h0,double h1)345 angleCoeff01(double h,
346              double h0,
347              double h1)
348 {
349     assert(0 <= h && h <= 360 && 0 <= h0 && h0 <= 360 && 0 <= h1 && h1 <= 360);
350     if ( h1 == (h0 + 360.) ) {
351         // interval is the whole hue circle
352         return 1.;
353     }
354     if ( !angleWithinRange(h, h0, h1) ) {
355         return 0.;
356     }
357     if (h1 == h0) {
358         return 1.;
359     }
360     if (h1 < h0) {
361         h1 += 360;
362         if (h < h0) {
363             h += 360;
364         }
365     }
366     assert(h0 <= h && h <= h1);
367 
368     return (h - h0) / (h1 - h0);
369 }
370 
371 // returns:
372 // - 0 if outside of [h0, h1]
373 // - 1 at h0
374 // - 0 at h1
375 // - linear from h0 to h1
376 static inline
377 double
angleCoeff10(double h,double h0,double h1)378 angleCoeff10(double h,
379              double h0,
380              double h1)
381 {
382     assert(0 <= h && h <= 360 && 0 <= h0 && h0 <= 360 && 0 <= h1 && h1 <= 360);
383     if ( !angleWithinRange(h, h0, h1) ) {
384         return 0.;
385     }
386     if (h1 == h0) {
387         return 1.;
388     }
389     if (h1 < h0) {
390         h1 += 360;
391         if (h < h0) {
392             h += 360;
393         }
394     }
395     assert(h0 <= h && h <= h1);
396 
397     return (h1 - h) / (h1 - h0);
398 }
399 
400 class HSVToolProcessorBase
401     : public ImageProcessor
402 {
403 protected:
404     const Image *_srcImg;
405     const Image *_maskImg;
406     OutputAlphaEnum _outputAlpha;
407     bool _premult;
408     int _premultChannel;
409     bool _doMasking;
410     double _mix;
411     bool _maskInvert;
412 
413 public:
414 
HSVToolProcessorBase(ImageEffect & instance)415     HSVToolProcessorBase(ImageEffect &instance)
416         : ImageProcessor(instance)
417         , _srcImg(NULL)
418         , _maskImg(NULL)
419         , _outputAlpha(eOutputAlphaSource)
420         , _premult(false)
421         , _premultChannel(3)
422         , _doMasking(false)
423         , _mix(1.)
424         , _maskInvert(false)
425         , _clampBlack(true)
426         , _clampWhite(true)
427     {
428         assert( angleWithinRange(0, 350, 10) );
429         assert( angleWithinRange(0, 0, 10) );
430         assert( !angleWithinRange(0, 5, 10) );
431         assert( !angleWithinRange(0, 10, 350) );
432         assert( angleWithinRange(0, 10, 0) );
433         assert( angleWithinRange(0, 10, 5) );
434         assert(normalizeAngle(-10) == 350);
435         assert(normalizeAngle(-370) == 350);
436         assert(normalizeAngle(-730) == 350);
437         assert(normalizeAngle(370) == 10);
438         assert(normalizeAngle(10) == 10);
439         assert(normalizeAngle(730) == 10);
440     }
441 
setSrcImg(const Image * v)442     void setSrcImg(const Image *v) {_srcImg = v; }
443 
setMaskImg(const Image * v,bool maskInvert)444     void setMaskImg(const Image *v,
445                     bool maskInvert) { _maskImg = v; _maskInvert = maskInvert; }
446 
doMasking(bool v)447     void doMasking(bool v) {_doMasking = v; }
448 
setValues(const HSVToolValues & values,bool clampBlack,bool clampWhite,OutputAlphaEnum outputAlpha,bool premult,int premultChannel,double mix)449     void setValues(const HSVToolValues& values,
450                    bool clampBlack,
451                    bool clampWhite,
452                    OutputAlphaEnum outputAlpha,
453                    bool premult,
454                    int premultChannel,
455                    double mix)
456     {
457         _values = values;
458         // set the intervals
459         // the hue interval is from the right of h0 to the left of h1
460         double h0 = _values.hueRange[0];
461         double h1 = _values.hueRange[1];
462         if ( h1 == (h0 + 360.) ) {
463             // special case: select any hue (useful to rotate all colors)
464             _values.hueRange[0] = 0.;
465             _values.hueRange[1] = 360.;
466             _values.hueRolloff = 0.;
467             _values.hueRangeWithRolloff[0] = 0.;
468             _values.hueRangeWithRolloff[1] = 360.;
469             _values.hueMean = 0.;
470         } else {
471             h0 = normalizeAngle(h0);
472             h1 = normalizeAngle(h1);
473             if (h1 < h0) {
474                 std::swap(h0, h1);
475             }
476             // take the smallest of both angles
477             if ( (h1 - h0) > 180. ) {
478                 std::swap(h0, h1);
479             }
480             assert (0 <= h0 && h0 <= 360 && 0 <= h1 && h1 <= 360);
481             _values.hueRange[0] = h0;
482             _values.hueRange[1] = h1;
483             // set strict bounds on rolloff
484             if (_values.hueRolloff < 0.) {
485                 _values.hueRolloff = 0.;
486             } else if (_values.hueRolloff >= 180.) {
487                 _values.hueRolloff = 180.;
488             }
489             _values.hueRangeWithRolloff[0] = normalizeAngle(h0 - _values.hueRolloff);
490             _values.hueRangeWithRolloff[1] = normalizeAngle(h1 + _values.hueRolloff);
491             _values.hueMean = normalizeAngle(h0 + normalizeAngleSigned(h1 - h0) / 2);
492         }
493         if (_values.satRange[1] < _values.satRange[0]) {
494             std::swap(_values.satRange[0], _values.satRange[1]);
495         }
496         if (_values.satRolloff < 0.) {
497             _values.satRolloff = 0.;
498         }
499         if (_values.valRange[1] < _values.valRange[0]) {
500             std::swap(_values.valRange[0], _values.valRange[1]);
501         }
502         if (_values.valRolloff < 0.) {
503             _values.valRolloff = 0.;
504         }
505         _clampBlack = clampBlack;
506         _clampWhite = clampWhite;
507         _outputAlpha = outputAlpha;
508         _premult = premult;
509         _premultChannel = premultChannel;
510         _mix = mix;
511     } // setValues
512 
hsvtool(float r,float g,float b,float * hcoeff,float * scoeff,float * vcoeff,float * rout,float * gout,float * bout)513     void hsvtool(float r,
514                  float g,
515                  float b,
516                  float *hcoeff,
517                  float *scoeff,
518                  float *vcoeff,
519                  float *rout,
520                  float *gout,
521                  float *bout)
522     {
523         float h, s, v;
524 
525         Color::rgb_to_hsv(r, g, b, &h, &s, &v);
526 
527         h *= 360. / OFXS_HUE_CIRCLE;
528         const double h0 = _values.hueRange[0];
529         const double h1 = _values.hueRange[1];
530         const double h0mrolloff = _values.hueRangeWithRolloff[0];
531         const double h1prolloff = _values.hueRangeWithRolloff[1];
532         // the affected
533         if ( angleWithinRange(h, h0, h1) ) {
534             *hcoeff = 1.f;
535         } else {
536             double c0 = 0.;
537             double c1 = 0.;
538             // check if we are in the rolloff area
539             if ( angleWithinRange(h, h0mrolloff, h0) ) {
540                 c0 = angleCoeff01(h, h0mrolloff, h0);
541             }
542             if ( angleWithinRange(h, h1, h1prolloff) ) {
543                 c1 = angleCoeff10(h, h1, h1prolloff);
544             }
545             *hcoeff = (float)std::max(c0, c1);
546         }
547         assert(0 <= *hcoeff && *hcoeff <= 1.);
548         const double s0 = _values.satRange[0];
549         const double s1 = _values.satRange[1];
550         const double s0mrolloff = s0 - _values.satRolloff;
551         const double s1prolloff = s1 + _values.satRolloff;
552         if ( (s0 <= s) && (s <= s1) ) {
553             *scoeff = 1.f;
554         } else if ( (s0mrolloff <= s) && (s <= s0) ) {
555             *scoeff = (float)(s - s0mrolloff) / (float)_values.satRolloff;
556         } else if ( (s1 <= s) && (s <= s1prolloff) ) {
557             *scoeff = (float)(s1prolloff - s) / (float)_values.satRolloff;
558         } else {
559             *scoeff = 0.f;
560         }
561         assert(0 <= *scoeff && *scoeff <= 1.);
562         const double v0 = _values.valRange[0];
563         const double v1 = _values.valRange[1];
564         const double v0mrolloff = v0 - _values.valRolloff;
565         const double v1prolloff = v1 + _values.valRolloff;
566         if ( (v0 <= v) && (v <= v1) ) {
567             *vcoeff = 1.f;
568         } else if ( (v0mrolloff <= v) && (v <= v0) ) {
569             *vcoeff = (float)(v - v0mrolloff) / (float)_values.valRolloff;
570         } else if ( (v1 <= v) && (v <= v1prolloff) ) {
571             *vcoeff = (float)(v1prolloff - v) / (float)_values.valRolloff;
572         } else {
573             *vcoeff = 0.f;
574         }
575         assert(0 <= *vcoeff && *vcoeff <= 1.);
576         float coeff = std::min(std::min(*hcoeff, *scoeff), *vcoeff);
577         assert(0 <= coeff && coeff <= 1.);
578         if (coeff <= 0.) {
579             *rout = r;
580             *gout = g;
581             *bout = b;
582         } else {
583             //h += coeff * (float)_values.hueRotation;
584             h += coeff * ( (float)_values.hueRotation + (_values.hueRotationGain - 1.) * normalizeAngleSigned(h - _values.hueMean) );
585             s += coeff * ( (float)_values.satAdjust + (_values.satAdjustGain - 1.) * (s - (s0 + s1) / 2) );
586             if (s < 0) {
587                 s = 0;
588             }
589             v += coeff * ( (float)_values.valAdjust + (_values.valAdjustGain - 1.) * (v - (v0 + v1) / 2) );
590             h *= OFXS_HUE_CIRCLE / 360.;
591             Color::hsv_to_rgb(h, s, v, rout, gout, bout);
592         }
593         if (_clampBlack) {
594             *rout = std::max(0.f, *rout);
595             *gout = std::max(0.f, *gout);
596             *bout = std::max(0.f, *bout);
597         }
598         if (_clampWhite) {
599             *rout = std::min(1.f, *rout);
600             *gout = std::min(1.f, *gout);
601             *bout = std::min(1.f, *bout);
602         }
603     } // hsvtool
604 
605 private:
606     HSVToolValues _values;
607     bool _clampBlack;
608     bool _clampWhite;
609 };
610 
611 
612 template <class PIX, int nComponents, int maxValue>
613 class HSVToolProcessor
614     : public HSVToolProcessorBase
615 {
616 public:
HSVToolProcessor(ImageEffect & instance)617     HSVToolProcessor(ImageEffect &instance)
618         : HSVToolProcessorBase(instance)
619     {
620     }
621 
multiThreadProcessImages(OfxRectI procWindow)622     void multiThreadProcessImages(OfxRectI procWindow)
623     {
624         assert(nComponents == 3 || nComponents == 4);
625         assert(_dstImg);
626         float unpPix[4];
627         float tmpPix[4];
628         // only premultiply output if keeping the source alpha
629         const bool premultOut = _premult && (_outputAlpha == eOutputAlphaSource);
630         for (int y = procWindow.y1; y < procWindow.y2; y++) {
631             if ( _effect.abort() ) {
632                 break;
633             }
634 
635             PIX *dstPix = (PIX *) _dstImg->getPixelAddress(procWindow.x1, y);
636 
637             for (int x = procWindow.x1; x < procWindow.x2; x++) {
638                 const PIX *srcPix = (const PIX *)  (_srcImg ? _srcImg->getPixelAddress(x, y) : 0);
639                 ofxsUnPremult<PIX, nComponents, maxValue>(srcPix, unpPix, _premult, _premultChannel);
640                 float hcoeff, scoeff, vcoeff;
641                 hsvtool(unpPix[0], unpPix[1], unpPix[2], &hcoeff, &scoeff, &vcoeff, &tmpPix[0], &tmpPix[1], &tmpPix[2]);
642                 ofxsPremultMaskMixPix<PIX, nComponents, maxValue, true>(tmpPix, premultOut, _premultChannel, x, y, srcPix, _doMasking, _maskImg, (float)_mix, _maskInvert, dstPix);
643                 // if output alpha is not source alpha, set it to the right value
644                 if ( (nComponents == 4) && (_outputAlpha != eOutputAlphaSource) ) {
645                     float a = 0.f;
646                     switch (_outputAlpha) {
647                     case eOutputAlphaSource:
648                         break;
649                     case eOutputAlphaHue:
650                         a = hcoeff;
651                         break;
652                     case eOutputAlphaSaturation:
653                         a = scoeff;
654                         break;
655                     case eOutputAlphaBrightness:
656                         a = vcoeff;
657                         break;
658                     case eOutputAlphaHueSaturation:
659                         a = std::min(hcoeff, scoeff);
660                         break;
661                     case eOutputAlphaHueBrightness:
662                         a = std::min(hcoeff, vcoeff);
663                         break;
664                     case eOutputAlphaSaturationBrightness:
665                         a = std::min(scoeff, vcoeff);
666                         break;
667                     case eOutputAlphaAll:
668                         a = std::min(std::min(hcoeff, scoeff), vcoeff);
669                         break;
670                     }
671                     if (_doMasking) {
672                         // we do, get the pixel from the mask
673                         const PIX* maskPix = _maskImg ? (const PIX *)_maskImg->getPixelAddress(x, y) : 0;
674                         float maskScale;
675                         // figure the scale factor from that pixel
676                         if (maskPix == 0) {
677                             maskScale = _maskInvert ? 1.f : 0.f;
678                         } else {
679                             maskScale = *maskPix / float(maxValue);
680                             if (_maskInvert) {
681                                 maskScale = 1.f - maskScale;
682                             }
683                         }
684                         a = std::min(a, maskScale);
685                     }
686                     dstPix[3] = maxValue * a;
687                 }
688 
689                 // increment the dst pixel
690                 dstPix += nComponents;
691             }
692         }
693     } // multiThreadProcessImages
694 };
695 
696 typedef struct HSVColor
697 {
HSVColorHSVColor698     HSVColor() : h(0), s(0), v(0) {}
699 
700     double h, s, v;
701 } HSVColor;
702 typedef struct HSVColorF
703 {
HSVColorFHSVColorF704     HSVColorF() : h(0), s(0), v(0) {}
705 
706     float h, s, v;
707 } HSVColorF;
708 
709 
710 class HueMeanProcessorBase
711     : public ImageProcessor
712 {
713 protected:
714     Mutex _mutex; //< this is used so we can multi-thread the analysis and protect the shared results
715     unsigned long _count;
716     double _sumsinh, _sumcosh;
717 
718 public:
HueMeanProcessorBase(ImageEffect & instance)719     HueMeanProcessorBase(ImageEffect &instance)
720         : ImageProcessor(instance)
721         , _mutex()
722         , _count(0)
723         , _sumsinh(0.)
724         , _sumcosh(0.)
725     {
726     }
727 
~HueMeanProcessorBase()728     ~HueMeanProcessorBase()
729     {
730     }
731 
getResult()732     double getResult()
733     {
734         if (_count <= 0) {
735             return 0;
736         } else {
737             double meansinh = _sumsinh / _count;
738             double meancosh = _sumcosh / _count;
739 
740             // angle mean and sdev from https://en.wikipedia.org/wiki/Directional_statistics#Measures_of_location_and_spread
741             return normalizeAngle(std::atan2(meansinh, meancosh) * 180 / M_PI);
742             //*huesdev = std::sqrt(std::max(0., -std::log(meansinh*meansinh+meancosh*meancosh)))*180/M_PI;
743         }
744     }
745 
746 protected:
addResults(double sumsinh,double sumcosh,unsigned long count)747     void addResults(double sumsinh,
748                     double sumcosh,
749                     unsigned long count)
750     {
751         _mutex.lock();
752         _sumsinh += sumsinh;
753         _sumcosh += sumcosh;
754         _count += count;
755         _mutex.unlock();
756     }
757 };
758 
759 template <class PIX, int nComponents, int maxValue>
760 class HueMeanProcessor
761     : public HueMeanProcessorBase
762 {
763 public:
HueMeanProcessor(ImageEffect & instance)764     HueMeanProcessor(ImageEffect &instance)
765         : HueMeanProcessorBase(instance)
766     {
767     }
768 
~HueMeanProcessor()769     ~HueMeanProcessor()
770     {
771     }
772 
773 private:
774 
pixToHSV(const PIX * p,HSVColorF * hsv)775     void pixToHSV(const PIX *p,
776                   HSVColorF* hsv)
777     {
778         if ( (nComponents == 4) || (nComponents == 3) ) {
779             float r, g, b;
780             r = p[0] / (float)maxValue;
781             g = p[1] / (float)maxValue;
782             b = p[2] / (float)maxValue;
783             Color::rgb_to_hsv(r, g, b, &hsv->h, &hsv->s, &hsv->v);
784             hsv->h *= 360 / OFXS_HUE_CIRCLE;
785         } else {
786             *hsv = HSVColorF();
787         }
788     }
789 
multiThreadProcessImages(OfxRectI procWindow)790     void multiThreadProcessImages(OfxRectI procWindow) OVERRIDE FINAL
791     {
792         double sumsinh = 0.;
793         double sumcosh = 0.;
794         unsigned long count = 0;
795 
796         assert(_dstImg->getBounds().x1 <= procWindow.x1 && procWindow.y2 <= _dstImg->getBounds().y2 &&
797                _dstImg->getBounds().y1 <= procWindow.y1 && procWindow.y2 <= _dstImg->getBounds().y2);
798         for (int y = procWindow.y1; y < procWindow.y2; ++y) {
799             if ( _effect.abort() ) {
800                 break;
801             }
802 
803             PIX *dstPix = (PIX *) _dstImg->getPixelAddress(procWindow.x1, y);
804 
805             // partial sums to avoid underflows
806             double sumsinhLine = 0.;
807             double sumcoshLine = 0.;
808 
809             for (int x = procWindow.x1; x < procWindow.x2; ++x) {
810                 HSVColorF hsv;
811                 pixToHSV(dstPix, &hsv);
812                 if ( (hsv.s > MIN_SATURATION) && (hsv.v > MIN_VALUE) ) {
813                     // only take into account pixels that really have a hue
814                     sumsinhLine += std::sin(hsv.h * M_PI / 180);
815                     sumcoshLine += std::cos(hsv.h * M_PI / 180);
816                     ++count;
817                 }
818 
819                 dstPix += nComponents;
820             }
821             sumsinh += sumsinhLine;
822             sumcosh += sumcoshLine;
823         }
824 
825         addResults(sumsinh, sumcosh, count);
826     }
827 };
828 
829 class HSVRangeProcessorBase
830     : public ImageProcessor
831 {
832 protected:
833     Mutex _mutex; //< this is used so we can multi-thread the analysis and protect the shared results
834     float _hmean;
835 
836 private:
837     float _dhmin; // -180..180
838     float _dhmax; // -180..180
839     float _smin;
840     float _smax;
841     float _vmin;
842     float _vmax;
843 
844 public:
HSVRangeProcessorBase(ImageEffect & instance)845     HSVRangeProcessorBase(ImageEffect &instance)
846         : ImageProcessor(instance)
847         , _mutex()
848         , _hmean(0.f)
849         , _dhmin(FLT_MAX)
850         , _dhmax(-FLT_MAX)
851         , _smin(FLT_MAX)
852         , _smax(-FLT_MAX)
853         , _vmin(FLT_MAX)
854         , _vmax(-FLT_MAX)
855     {
856     }
857 
~HSVRangeProcessorBase()858     ~HSVRangeProcessorBase()
859     {
860     }
861 
setHueMean(float hmean)862     void setHueMean(float hmean)
863     {
864         _hmean = hmean;
865     }
866 
getResults(HSVColor * hsvmin,HSVColor * hsvmax)867     void getResults(HSVColor *hsvmin,
868                     HSVColor *hsvmax)
869     {
870         if (_dhmax - _dhmin > 179.9) {
871             // more than half circle, take the full circle
872             hsvmin->h = 0.;
873             hsvmax->h = 360.;
874         } else {
875             hsvmin->h = normalizeAngle(_hmean + _dhmin);
876             hsvmax->h = normalizeAngle(_hmean + _dhmax);
877         }
878         hsvmin->s = _smin;
879         hsvmax->s = _smax;
880         hsvmin->v = _vmin;
881         hsvmax->v = _vmax;
882     }
883 
884 protected:
addResults(const float dhmin,const float dhmax,const float smin,const float smax,const float vmin,const float vmax)885     void addResults(const float dhmin,
886                     const float dhmax,
887                     const float smin,
888                     const float smax,
889                     const float vmin,
890                     const float vmax)
891     {
892         _mutex.lock();
893         if (dhmin < _dhmin) { _dhmin = dhmin; }
894         if (dhmax > _dhmax) { _dhmax = dhmax; }
895         if (smin < _smin) { _smin = smin; }
896         if (smax > _smax) { _smax = smax; }
897         if (vmin < _vmin) { _vmin = vmin; }
898         if (vmax > _vmax) { _vmax = vmax; }
899         _mutex.unlock();
900     }
901 };
902 
903 template <class PIX, int nComponents, int maxValue>
904 class HSVRangeProcessor
905     : public HSVRangeProcessorBase
906 {
907 public:
HSVRangeProcessor(ImageEffect & instance)908     HSVRangeProcessor(ImageEffect &instance)
909         : HSVRangeProcessorBase(instance)
910     {
911     }
912 
~HSVRangeProcessor()913     ~HSVRangeProcessor()
914     {
915     }
916 
917 private:
918 
pixToHSV(const PIX * p,HSVColorF * hsv)919     void pixToHSV(const PIX *p,
920                   HSVColorF* hsv)
921     {
922         if ( (nComponents == 4) || (nComponents == 3) ) {
923             float r, g, b;
924             r = p[0] / (float)maxValue;
925             g = p[1] / (float)maxValue;
926             b = p[2] / (float)maxValue;
927             Color::rgb_to_hsv(r, g, b, &hsv->h, &hsv->s, &hsv->v);
928             hsv->h *= 360 / OFXS_HUE_CIRCLE;
929         } else {
930             *hsv = HSVColorF();
931         }
932     }
933 
multiThreadProcessImages(OfxRectI procWindow)934     void multiThreadProcessImages(OfxRectI procWindow) OVERRIDE FINAL
935     {
936         assert(_dstImg->getBounds().x1 <= procWindow.x1 && procWindow.y2 <= _dstImg->getBounds().y2 &&
937                _dstImg->getBounds().y1 <= procWindow.y1 && procWindow.y2 <= _dstImg->getBounds().y2);
938         float dhmin = 0.;
939         float dhmax = 0.;
940         float smin = FLT_MAX;
941         float smax = -FLT_MAX;
942         float vmin = FLT_MAX;
943         float vmax = -FLT_MAX;
944         for (int y = procWindow.y1; y < procWindow.y2; ++y) {
945             if ( _effect.abort() ) {
946                 break;
947             }
948 
949             PIX *dstPix = (PIX *) _dstImg->getPixelAddress(procWindow.x1, y);
950 
951             for (int x = procWindow.x1; x < procWindow.x2; ++x) {
952                 HSVColorF hsv;
953                 pixToHSV(dstPix, &hsv);
954                 if ( (hsv.s > MIN_SATURATION) && (hsv.v > MIN_VALUE) ) {
955                     float dh = normalizeAngleSigned(hsv.h - _hmean); // relative angle with hmean
956                     if (dh < dhmin) { dhmin = dh; }
957                     if (dh > dhmax) { dhmax = dh; }
958                 }
959                 if (hsv.s < smin) { smin = hsv.s; }
960                 if (hsv.s > smax) { smax = hsv.s; }
961                 if (hsv.v < vmin) { vmin = hsv.v; }
962                 if (hsv.v > vmax) { vmax = hsv.v; }
963 
964                 dstPix += nComponents;
965             }
966         }
967 
968         addResults(dhmin, dhmax, smin, smax, vmin, vmax);
969     }
970 };
971 
972 
973 ////////////////////////////////////////////////////////////////////////////////
974 /** @brief The plugin that does our work */
975 class HSVToolPlugin
976     : public ImageEffect
977 {
978 public:
979     /** @brief ctor */
HSVToolPlugin(OfxImageEffectHandle handle)980     HSVToolPlugin(OfxImageEffectHandle handle)
981         : ImageEffect(handle)
982         , _dstClip(NULL)
983         , _srcClip(NULL)
984         , _maskClip(NULL)
985         , _srcColor(NULL)
986         , _dstColor(NULL)
987         , _hueRange(NULL)
988         , _hueRotation(NULL)
989         , _hueRotationGain(NULL)
990         , _hueRangeRolloff(NULL)
991         , _saturationRange(NULL)
992         , _saturationAdjustment(NULL)
993         , _saturationAdjustmentGain(NULL)
994         , _saturationRangeRolloff(NULL)
995         , _brightnessRange(NULL)
996         , _brightnessAdjustment(NULL)
997         , _brightnessAdjustmentGain(NULL)
998         , _brightnessRangeRolloff(NULL)
999         , _clampBlack(NULL)
1000         , _clampWhite(NULL)
1001         , _outputAlpha(NULL)
1002         , _premult(NULL)
1003         , _premultChannel(NULL)
1004         , _mix(NULL)
1005         , _maskApply(NULL)
1006         , _maskInvert(NULL)
1007         , _premultChanged(NULL)
1008     {
1009         _dstClip = fetchClip(kOfxImageEffectOutputClipName);
1010         assert( _dstClip && (!_dstClip->isConnected() || _dstClip->getPixelComponents() == ePixelComponentRGB ||
1011                              _dstClip->getPixelComponents() == ePixelComponentRGBA) );
1012         _srcClip = getContext() == eContextGenerator ? NULL : fetchClip(kOfxImageEffectSimpleSourceClipName);
1013         assert( (!_srcClip && getContext() == eContextGenerator) ||
1014                 ( _srcClip && (!_srcClip->isConnected() || _srcClip->getPixelComponents() ==  ePixelComponentRGB ||
1015                                _srcClip->getPixelComponents() == ePixelComponentRGBA) ) );
1016         _maskClip = fetchClip(getContext() == eContextPaint ? "Brush" : "Mask");
1017         assert(!_maskClip || !_maskClip->isConnected() || _maskClip->getPixelComponents() == ePixelComponentAlpha);
1018 
1019         _btmLeft = fetchDouble2DParam(kParamRectangleInteractBtmLeft);
1020         _size = fetchDouble2DParam(kParamRectangleInteractSize);
1021         _enableRectangle = fetchBooleanParam(kParamEnableRectangle);
1022         assert(_btmLeft && _size && _enableRectangle);
1023         _setSrcFromRectangle = fetchPushButtonParam(kParamSetSrcFromRectangle);
1024         assert(_setSrcFromRectangle);
1025         _srcColor = fetchRGBParam(kParamSrcColor);
1026         _dstColor = fetchRGBParam(kParamDstColor);
1027         _hueRange = fetchDouble2DParam(kParamHueRange);
1028         _hueRotation = fetchDoubleParam(kParamHueRotation);
1029         _hueRotationGain = fetchDoubleParam(kParamHueRotationGain);
1030         _hueRangeRolloff = fetchDoubleParam(kParamHueRangeRolloff);
1031         _saturationRange = fetchDouble2DParam(kParamSaturationRange);
1032         _saturationAdjustment = fetchDoubleParam(kParamSaturationAdjustment);
1033         _saturationAdjustmentGain = fetchDoubleParam(kParamSaturationAdjustmentGain);
1034         _saturationRangeRolloff = fetchDoubleParam(kParamSaturationRangeRolloff);
1035         _brightnessRange = fetchDouble2DParam(kParamBrightnessRange);
1036         _brightnessAdjustment = fetchDoubleParam(kParamBrightnessAdjustment);
1037         _brightnessAdjustmentGain = fetchDoubleParam(kParamBrightnessAdjustmentGain);
1038         _brightnessRangeRolloff = fetchDoubleParam(kParamBrightnessRangeRolloff);
1039         assert(_srcColor && _dstColor &&
1040                _hueRange && _hueRotation && _hueRotationGain && _hueRangeRolloff &&
1041                _saturationRange && _saturationAdjustment && _saturationAdjustmentGain && _saturationRangeRolloff &&
1042                _brightnessRange && _brightnessAdjustment && _brightnessAdjustmentGain && _brightnessRangeRolloff);
1043         _clampBlack = fetchBooleanParam(kParamClampBlack);
1044         _clampWhite = fetchBooleanParam(kParamClampWhite);
1045         assert(_clampBlack && _clampWhite);
1046         _outputAlpha = fetchChoiceParam(kParamOutputAlpha);
1047         assert(_outputAlpha);
1048         _premult = fetchBooleanParam(kParamPremult);
1049         _premultChannel = fetchChoiceParam(kParamPremultChannel);
1050         assert(_premult && _premultChannel);
1051         _mix = fetchDoubleParam(kParamMix);
1052         _maskApply = ( ofxsMaskIsAlwaysConnected( OFX::getImageEffectHostDescription() ) && paramExists(kParamMaskApply) ) ? fetchBooleanParam(kParamMaskApply) : 0;
1053         _maskInvert = fetchBooleanParam(kParamMaskInvert);
1054         assert(_mix && _maskInvert);
1055         _premultChanged = fetchBooleanParam(kParamPremultChanged);
1056         assert(_premultChanged);
1057 
1058 
1059         // honor kParamDefaultsNormalised
1060         if ( paramExists(kParamDefaultsNormalised) ) {
1061             // Some hosts (e.g. Resolve) may not support normalized defaults (setDefaultCoordinateSystem(eCoordinatesNormalised))
1062             // handle these ourselves!
1063             BooleanParam* param = fetchBooleanParam(kParamDefaultsNormalised);
1064             assert(param);
1065             bool normalised = param->getValue();
1066             if (normalised) {
1067                 OfxPointD size = getProjectExtent();
1068                 OfxPointD origin = getProjectOffset();
1069                 OfxPointD p;
1070                 // we must denormalise all parameters for which setDefaultCoordinateSystem(eCoordinatesNormalised) couldn't be done
1071                 beginEditBlock(kParamDefaultsNormalised);
1072                 p = _btmLeft->getValue();
1073                 _btmLeft->setValue(p.x * size.x + origin.x, p.y * size.y + origin.y);
1074                 p = _size->getValue();
1075                 _size->setValue(p.x * size.x, p.y * size.y);
1076                 param->setValue(false);
1077                 endEditBlock();
1078             }
1079         }
1080 
1081         // finally...
1082         syncPrivateData();
1083     }
1084 
1085 private:
1086     /* Override the render */
1087     virtual void render(const RenderArguments &args) OVERRIDE FINAL;
1088 
1089     /* set up and run a processor */
1090     void setupAndProcess(HSVToolProcessorBase &, const RenderArguments &args);
1091 
1092     virtual bool isIdentity(const IsIdentityArguments &args, Clip * &identityClip, double &identityTime, int& view, std::string& plane) OVERRIDE FINAL;
1093     virtual void changedParam(const InstanceChangedArgs &args, const std::string &paramName) OVERRIDE FINAL;
1094 
1095     /** @brief called when a clip has just been changed in some way (a rewire maybe) */
1096     virtual void changedClip(const InstanceChangedArgs &args, const std::string &clipName) OVERRIDE FINAL;
1097     virtual void getClipPreferences(ClipPreferencesSetter &clipPreferences) OVERRIDE FINAL;
1098 
1099     /** @brief The sync private data action, called when the effect needs to sync any private data to persistent parameters */
syncPrivateData(void)1100     virtual void syncPrivateData(void) OVERRIDE FINAL
1101     {
1102         // update visibility
1103         bool enableRectangle = _enableRectangle->getValue();
1104         _btmLeft->setIsSecretAndDisabled(!enableRectangle);
1105         _size->setIsSecretAndDisabled(!enableRectangle);
1106         _setSrcFromRectangle->setIsSecretAndDisabled(!enableRectangle);
1107         _srcColor->setEnabled(!enableRectangle);
1108     }
1109 
1110     // compute computation window in srcImg
1111     bool computeWindow(const Image* srcImg, double time, OfxRectI *analysisWindow);
1112 
1113     // update image statistics
1114     void setSrcFromRectangle(const Image* srcImg, double time, const OfxRectI& analysisWindow);
1115 
1116     void setSrcFromRectangleProcess(HueMeanProcessorBase &huemeanprocessor, HSVRangeProcessorBase &rangeprocessor, const Image* srcImg, double /*time*/, const OfxRectI &analysisWindow, double *hmean, HSVColor *hsvmin, HSVColor *hsvmax);
1117 
1118     template <class PIX, int nComponents, int maxValue>
setSrcFromRectangleComponentsDepth(const Image * srcImg,double time,const OfxRectI & analysisWindow,double * hmean,HSVColor * hsvmin,HSVColor * hsvmax)1119     void setSrcFromRectangleComponentsDepth(const Image* srcImg,
1120                                             double time,
1121                                             const OfxRectI &analysisWindow,
1122                                             double *hmean,
1123                                             HSVColor *hsvmin,
1124                                             HSVColor *hsvmax)
1125     {
1126         HueMeanProcessor<PIX, nComponents, maxValue> fred1(*this);
1127         HSVRangeProcessor<PIX, nComponents, maxValue> fred2(*this);
1128         setSrcFromRectangleProcess(fred1, fred2, srcImg, time, analysisWindow, hmean, hsvmin, hsvmax);
1129     }
1130 
1131     template <int nComponents>
setSrcFromRectangleComponents(const Image * srcImg,double time,const OfxRectI & analysisWindow,double * hmean,HSVColor * hsvmin,HSVColor * hsvmax)1132     void setSrcFromRectangleComponents(const Image* srcImg,
1133                                        double time,
1134                                        const OfxRectI &analysisWindow,
1135                                        double *hmean,
1136                                        HSVColor *hsvmin,
1137                                        HSVColor *hsvmax)
1138     {
1139         BitDepthEnum srcBitDepth = srcImg->getPixelDepth();
1140 
1141         switch (srcBitDepth) {
1142         case eBitDepthUByte: {
1143             setSrcFromRectangleComponentsDepth<unsigned char, nComponents, 255>(srcImg, time, analysisWindow, hmean, hsvmin, hsvmax);
1144             break;
1145         }
1146         case eBitDepthUShort: {
1147             setSrcFromRectangleComponentsDepth<unsigned short, nComponents, 65535>(srcImg, time, analysisWindow, hmean, hsvmin, hsvmax);
1148             break;
1149         }
1150         case eBitDepthFloat: {
1151             setSrcFromRectangleComponentsDepth<float, nComponents, 1>(srcImg, time, analysisWindow, hmean, hsvmin, hsvmax);
1152             break;
1153         }
1154         default:
1155             throwSuiteStatusException(kOfxStatErrUnsupported);
1156         }
1157     }
1158 
1159 private:
1160     // do not need to delete these, the ImageEffect is managing them for us
1161     Clip *_dstClip;
1162     Clip *_srcClip;
1163     Clip *_maskClip;
1164     Double2DParam* _btmLeft;
1165     Double2DParam* _size;
1166     BooleanParam* _enableRectangle;
1167     PushButtonParam* _setSrcFromRectangle;
1168     RGBParam *_srcColor;
1169     RGBParam *_dstColor;
1170     Double2DParam *_hueRange;
1171     DoubleParam *_hueRotation;
1172     DoubleParam *_hueRotationGain;
1173     DoubleParam *_hueRangeRolloff;
1174     Double2DParam *_saturationRange;
1175     DoubleParam *_saturationAdjustment;
1176     DoubleParam *_saturationAdjustmentGain;
1177     DoubleParam *_saturationRangeRolloff;
1178     Double2DParam *_brightnessRange;
1179     DoubleParam *_brightnessAdjustment;
1180     DoubleParam *_brightnessAdjustmentGain;
1181     DoubleParam *_brightnessRangeRolloff;
1182     BooleanParam *_clampBlack;
1183     BooleanParam *_clampWhite;
1184     ChoiceParam *_outputAlpha;
1185     BooleanParam *_premult;
1186     ChoiceParam *_premultChannel;
1187     DoubleParam *_mix;
1188     BooleanParam *_maskApply;
1189     BooleanParam *_maskInvert;
1190     BooleanParam* _premultChanged; // set to true the first time the user connects src
1191 };
1192 
1193 
1194 ////////////////////////////////////////////////////////////////////////////////
1195 /** @brief render for the filter */
1196 
1197 ////////////////////////////////////////////////////////////////////////////////
1198 // basic plugin render function, just a skelington to instantiate templates from
1199 
1200 /* set up and run a processor */
1201 void
setupAndProcess(HSVToolProcessorBase & processor,const RenderArguments & args)1202 HSVToolPlugin::setupAndProcess(HSVToolProcessorBase &processor,
1203                                const RenderArguments &args)
1204 {
1205     const double time = args.time;
1206 
1207     auto_ptr<Image> dst( _dstClip->fetchImage(time) );
1208 
1209     if ( !dst.get() ) {
1210         throwSuiteStatusException(kOfxStatFailed);
1211     }
1212     BitDepthEnum dstBitDepth    = dst->getPixelDepth();
1213     PixelComponentEnum dstComponents  = dst->getPixelComponents();
1214     if ( ( dstBitDepth != _dstClip->getPixelDepth() ) ||
1215          ( dstComponents != _dstClip->getPixelComponents() ) ) {
1216         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong depth or components");
1217         throwSuiteStatusException(kOfxStatFailed);
1218     }
1219     if ( (dst->getRenderScale().x != args.renderScale.x) ||
1220          ( dst->getRenderScale().y != args.renderScale.y) ||
1221          ( ( dst->getField() != eFieldNone) /* for DaVinci Resolve */ && ( dst->getField() != args.fieldToRender) ) ) {
1222         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
1223         throwSuiteStatusException(kOfxStatFailed);
1224     }
1225 
1226     OutputAlphaEnum outputAlpha = (OutputAlphaEnum)_outputAlpha->getValueAtTime(time);
1227     if (outputAlpha != eOutputAlphaSource) {
1228         if (dstComponents != ePixelComponentRGBA) {
1229             setPersistentMessage(Message::eMessageError, "", "OFX Host dit not take into account output components");
1230             throwSuiteStatusException(kOfxStatErrImageFormat);
1231 
1232             return;
1233         }
1234     }
1235 
1236     auto_ptr<const Image> src( ( _srcClip && _srcClip->isConnected() ) ?
1237                                     _srcClip->fetchImage(time) : 0 );
1238     if ( src.get() ) {
1239         if ( (src->getRenderScale().x != args.renderScale.x) ||
1240              ( src->getRenderScale().y != args.renderScale.y) ||
1241              ( ( src->getField() != eFieldNone) /* for DaVinci Resolve */ && ( src->getField() != args.fieldToRender) ) ) {
1242             setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
1243             throwSuiteStatusException(kOfxStatFailed);
1244         }
1245         BitDepthEnum srcBitDepth      = src->getPixelDepth();
1246         PixelComponentEnum srcComponents = src->getPixelComponents();
1247         // set the components of _dstClip
1248         if ( (srcBitDepth != dstBitDepth) || ( (outputAlpha == eOutputAlphaSource) && (srcComponents != dstComponents) ) ) {
1249             throwSuiteStatusException(kOfxStatErrImageFormat);
1250         }
1251     }
1252     bool doMasking = ( ( !_maskApply || _maskApply->getValueAtTime(time) ) && _maskClip && _maskClip->isConnected() );
1253     auto_ptr<const Image> mask(doMasking ? _maskClip->fetchImage(time) : 0);
1254     if (doMasking) {
1255         if ( mask.get() ) {
1256             if ( (mask->getRenderScale().x != args.renderScale.x) ||
1257                  ( mask->getRenderScale().y != args.renderScale.y) ||
1258                  ( ( mask->getField() != eFieldNone) /* for DaVinci Resolve */ && ( mask->getField() != args.fieldToRender) ) ) {
1259                 setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
1260                 throwSuiteStatusException(kOfxStatFailed);
1261             }
1262         }
1263         bool maskInvert;
1264         _maskInvert->getValueAtTime(time, maskInvert);
1265         processor.doMasking(true);
1266         processor.setMaskImg(mask.get(), maskInvert);
1267     }
1268 
1269     processor.setDstImg( dst.get() );
1270     processor.setSrcImg( src.get() );
1271     processor.setRenderWindow(args.renderWindow);
1272 
1273     HSVToolValues values;
1274     _hueRange->getValueAtTime(time, values.hueRange[0], values.hueRange[1]);
1275     values.hueRangeWithRolloff[0] = values.hueRangeWithRolloff[1] = 0; // set in setValues()
1276     values.hueRotation = _hueRotation->getValueAtTime(time);
1277     values.hueRotationGain = _hueRotationGain->getValueAtTime(time);
1278     values.hueMean = 0; // set in setValues()
1279     values.hueRolloff = _hueRangeRolloff->getValueAtTime(time);
1280     _saturationRange->getValueAtTime(time, values.satRange[0], values.satRange[1]);
1281     values.satAdjust = _saturationAdjustment->getValueAtTime(time);
1282     values.satAdjustGain = _saturationAdjustmentGain->getValueAtTime(time);
1283     values.satRolloff = _saturationRangeRolloff->getValueAtTime(time);
1284     _brightnessRange->getValueAtTime(time, values.valRange[0], values.valRange[1]);
1285     values.valAdjust = _brightnessAdjustment->getValueAtTime(time);
1286     values.valAdjustGain = _brightnessAdjustmentGain->getValueAtTime(time);
1287     values.valRolloff = _brightnessRangeRolloff->getValueAtTime(time);
1288 
1289     bool clampBlack, clampWhite;
1290     _clampBlack->getValueAtTime(time, clampBlack);
1291     _clampWhite->getValueAtTime(time, clampWhite);
1292 
1293     bool premult;
1294     int premultChannel;
1295     _premult->getValueAtTime(time, premult);
1296     _premultChannel->getValueAtTime(time, premultChannel);
1297     double mix;
1298     _mix->getValueAtTime(time, mix);
1299 
1300     processor.setValues(values, clampBlack, clampWhite, outputAlpha, premult, premultChannel, mix);
1301     processor.process();
1302 } // HSVToolPlugin::setupAndProcess
1303 
1304 // the overridden render function
1305 void
render(const RenderArguments & args)1306 HSVToolPlugin::render(const RenderArguments &args)
1307 {
1308     // instantiate the render code based on the pixel depth of the dst clip
1309     BitDepthEnum dstBitDepth    = _dstClip->getPixelDepth();
1310     PixelComponentEnum dstComponents  = _dstClip->getPixelComponents();
1311 
1312     assert( kSupportsMultipleClipPARs   || !_srcClip || _srcClip->getPixelAspectRatio() == _dstClip->getPixelAspectRatio() );
1313     assert( kSupportsMultipleClipDepths || !_srcClip || _srcClip->getPixelDepth()       == _dstClip->getPixelDepth() );
1314     assert(dstComponents == ePixelComponentRGB || dstComponents == ePixelComponentRGBA);
1315     if (dstComponents == ePixelComponentRGBA) {
1316         switch (dstBitDepth) {
1317         case eBitDepthUByte: {
1318             HSVToolProcessor<unsigned char, 4, 255> fred(*this);
1319             setupAndProcess(fred, args);
1320             break;
1321         }
1322         case eBitDepthUShort: {
1323             HSVToolProcessor<unsigned short, 4, 65535> fred(*this);
1324             setupAndProcess(fred, args);
1325             break;
1326         }
1327         case eBitDepthFloat: {
1328             HSVToolProcessor<float, 4, 1> fred(*this);
1329             setupAndProcess(fred, args);
1330             break;
1331         }
1332         default:
1333             throwSuiteStatusException(kOfxStatErrUnsupported);
1334         }
1335     } else {
1336         assert(dstComponents == ePixelComponentRGB);
1337         switch (dstBitDepth) {
1338         case eBitDepthUByte: {
1339             HSVToolProcessor<unsigned char, 3, 255> fred(*this);
1340             setupAndProcess(fred, args);
1341             break;
1342         }
1343         case eBitDepthUShort: {
1344             HSVToolProcessor<unsigned short, 3, 65535> fred(*this);
1345             setupAndProcess(fred, args);
1346             break;
1347         }
1348         case eBitDepthFloat: {
1349             HSVToolProcessor<float, 3, 1> fred(*this);
1350             setupAndProcess(fred, args);
1351             break;
1352         }
1353         default:
1354             throwSuiteStatusException(kOfxStatErrUnsupported);
1355         }
1356     }
1357 } // HSVToolPlugin::render
1358 
1359 bool
isIdentity(const IsIdentityArguments & args,Clip * & identityClip,double &,int &,std::string &)1360 HSVToolPlugin::isIdentity(const IsIdentityArguments &args,
1361                           Clip * &identityClip,
1362                           double & /*identityTime*/
1363                           , int& /*view*/, std::string& /*plane*/)
1364 {
1365     if (!_srcClip || !_srcClip->isConnected()) {
1366         return false;
1367     }
1368     const double time = args.time;
1369     double mix = _mix->getValueAtTime(time);
1370 
1371     if (mix == 0.) {
1372         identityClip = _srcClip;
1373 
1374         return true;
1375     }
1376 
1377 
1378     if (_srcClip->getPixelComponents() == ePixelComponentRGBA) {
1379         // check cases where alpha is affected, even if colors don't change
1380         OutputAlphaEnum outputAlpha = (OutputAlphaEnum)_outputAlpha->getValueAtTime(time);
1381         if (outputAlpha != eOutputAlphaSource) {
1382             double hueMin, hueMax;
1383             _hueRange->getValueAtTime(time, hueMin, hueMax);
1384             bool alphaHue = (hueMin != 0. || hueMax != 360.);
1385             double satMin, satMax;
1386             _saturationRange->getValueAtTime(time, satMin, satMax);
1387             bool alphaSat = (satMin != 0. || satMax != 1.);
1388             double valMin, valMax;
1389             _brightnessRange->getValueAtTime(time, valMin, valMax);
1390             bool alphaVal = (valMin != 0. || valMax != 1.);
1391             switch (outputAlpha) {
1392             // coverity[dead_error_begin]
1393             case eOutputAlphaSource:
1394                 break;
1395             case eOutputAlphaHue:
1396                 if (alphaHue) {
1397                     return false;
1398                 }
1399                 break;
1400             case eOutputAlphaSaturation:
1401                 if (alphaSat) {
1402                     return false;
1403                 }
1404                 break;
1405             case eOutputAlphaBrightness:
1406                 if (alphaVal) {
1407                     return false;
1408                 }
1409                 break;
1410             case eOutputAlphaHueSaturation:
1411                 if (alphaHue || alphaSat) {
1412                     return false;
1413                 }
1414                 break;
1415             case eOutputAlphaHueBrightness:
1416                 if (alphaHue || alphaVal) {
1417                     return false;
1418                 }
1419                 break;
1420             case eOutputAlphaSaturationBrightness:
1421                 if (alphaSat || alphaVal) {
1422                     return false;
1423                 }
1424                 break;
1425             case eOutputAlphaAll:
1426                 if (alphaHue || alphaSat || alphaVal) {
1427                     return false;
1428                 }
1429                 break;
1430             }
1431         }
1432     }
1433 
1434     // isIdentity=true if hueRotation, satAdjust and valAdjust = 0.
1435     double hueRotation;
1436     _hueRotation->getValueAtTime(time, hueRotation);
1437     double saturationAdjustment;
1438     _saturationAdjustment->getValueAtTime(time, saturationAdjustment);
1439     double brightnessAdjustment;
1440     _brightnessAdjustment->getValueAtTime(time, brightnessAdjustment);
1441     if ( (hueRotation == 0.) && (saturationAdjustment == 0.) && (brightnessAdjustment == 0.) ) {
1442         identityClip = _srcClip;
1443 
1444         return true;
1445     }
1446 
1447     bool doMasking = ( ( !_maskApply || _maskApply->getValueAtTime(time) ) && _maskClip && _maskClip->isConnected() );
1448     if (doMasking) {
1449         bool maskInvert;
1450         _maskInvert->getValueAtTime(time, maskInvert);
1451         if (!maskInvert) {
1452             OfxRectI maskRoD;
1453             Coords::toPixelEnclosing(_maskClip->getRegionOfDefinition(time), args.renderScale, _maskClip->getPixelAspectRatio(), &maskRoD);
1454             // effect is identity if the renderWindow doesn't intersect the mask RoD
1455             if ( !Coords::rectIntersection<OfxRectI>(args.renderWindow, maskRoD, 0) ) {
1456                 identityClip = _srcClip;
1457 
1458                 return true;
1459             }
1460         }
1461     }
1462 
1463     return false;
1464 } // HSVToolPlugin::isIdentity
1465 
1466 bool
computeWindow(const Image * srcImg,double time,OfxRectI * analysisWindow)1467 HSVToolPlugin::computeWindow(const Image* srcImg,
1468                              double time,
1469                              OfxRectI *analysisWindow)
1470 {
1471     OfxRectD regionOfInterest;
1472     bool enableRectangle = _enableRectangle->getValueAtTime(time);
1473 
1474     if (!enableRectangle && _srcClip) {
1475         return false; // no analysis in this case
1476         /*
1477            // use the src region of definition as rectangle, but avoid infinite rectangle
1478            regionOfInterest = _srcClip->getRegionOfDefinition(time);
1479            OfxPointD size = getProjectSize();
1480            OfxPointD offset = getProjectOffset();
1481            if (regionOfInterest.x1 <= kOfxFlagInfiniteMin) {
1482             regionOfInterest.x1 = offset.x;
1483            }
1484            if (regionOfInterest.x2 >= kOfxFlagInfiniteMax) {
1485             regionOfInterest.x2 = offset.x + size.x;
1486            }
1487            if (regionOfInterest.y1 <= kOfxFlagInfiniteMin) {
1488             regionOfInterest.y1 = offset.y;
1489            }
1490            if (regionOfInterest.y2 >= kOfxFlagInfiniteMax) {
1491             regionOfInterest.y2 = offset.y + size.y;
1492            }
1493          */
1494     } else {
1495         _btmLeft->getValueAtTime(time, regionOfInterest.x1, regionOfInterest.y1);
1496         _size->getValueAtTime(time, regionOfInterest.x2, regionOfInterest.y2);
1497         regionOfInterest.x2 += regionOfInterest.x1;
1498         regionOfInterest.y2 += regionOfInterest.y1;
1499     }
1500     Coords::toPixelEnclosing(regionOfInterest,
1501                              srcImg->getRenderScale(),
1502                              srcImg->getPixelAspectRatio(),
1503                              analysisWindow);
1504 
1505     return Coords::rectIntersection(*analysisWindow, srcImg->getBounds(), analysisWindow);
1506 }
1507 
1508 void
setSrcFromRectangle(const Image * srcImg,double time,const OfxRectI & analysisWindow)1509 HSVToolPlugin::setSrcFromRectangle(const Image* srcImg,
1510                                    double time,
1511                                    const OfxRectI &analysisWindow)
1512 {
1513     double hmean = 0.;
1514     HSVColor hsvmin, hsvmax;
1515     PixelComponentEnum srcComponents  = srcImg->getPixelComponents();
1516 
1517     assert(srcComponents == ePixelComponentAlpha || srcComponents == ePixelComponentRGB || srcComponents == ePixelComponentRGBA);
1518     if (srcComponents == ePixelComponentAlpha) {
1519         setSrcFromRectangleComponents<1>(srcImg, time, analysisWindow, &hmean, &hsvmin, &hsvmax);
1520     } else if (srcComponents == ePixelComponentRGBA) {
1521         setSrcFromRectangleComponents<4>(srcImg, time, analysisWindow, &hmean, &hsvmin, &hsvmax);
1522     } else if (srcComponents == ePixelComponentRGB) {
1523         setSrcFromRectangleComponents<3>(srcImg, time, analysisWindow, &hmean, &hsvmin, &hsvmax);
1524     } else {
1525         // coverity[dead_error_line]
1526         throwSuiteStatusException(kOfxStatErrUnsupported);
1527 
1528         return;
1529     }
1530 
1531     if ( abort() ) {
1532         return;
1533     }
1534 
1535     float h = normalizeAngle(hmean);
1536     float s = (hsvmin.s + hsvmax.s) / 2;
1537     float v = (hsvmin.v + hsvmax.v) / 2;
1538     float r = 0.f;
1539     float g = 0.f;
1540     float b = 0.f;
1541     Color::hsv_to_rgb(h * OFXS_HUE_CIRCLE / 360., s, v, &r, &g, &b);
1542     double tor, tog, tob;
1543     _dstColor->getValueAtTime(time, tor, tog, tob);
1544     float toh, tos, tov;
1545     Color::rgb_to_hsv( (float)tor, (float)tog, (float)tob, &toh, &tos, &tov );
1546     double dh = normalizeAngleSigned(toh * 360. / OFXS_HUE_CIRCLE - h);
1547     // range is from mean+sdev*(GAUSSIAN_RANGE-GAUSSIAN_ROLLOFF) to mean+sdev*(GAUSSIAN_RANGE+GAUSSIAN_ROLLOFF)
1548     beginEditBlock("setSrcFromRectangle");
1549     _srcColor->setValue( fround(r, 4), fround(g, 4), fround(b, 4) );
1550     _hueRange->setValue( ffloor(hsvmin.h, 2), fceil(hsvmax.h, 2) );
1551     double hrange = hsvmax.h - hsvmin.h;
1552     if (hrange < 0) {
1553         hrange += 360.;
1554     }
1555     double hrolloff = std::min(hrange * DEFAULT_RECTANGLE_ROLLOFF, (360 - hrange) / 2);
1556     _hueRangeRolloff->setValue( ffloor(hrolloff, 2) );
1557     if (tov != 0.) { // no need to rotate if target color is black
1558         _hueRotation->setValue( fround(dh, 2) );
1559     }
1560     _saturationRange->setValue( ffloor(hsvmin.s, 4), fceil(hsvmax.s, 4) );
1561     _saturationRangeRolloff->setValue( ffloor( (hsvmax.s - hsvmin.s) * DEFAULT_RECTANGLE_ROLLOFF, 4 ) );
1562     if (tov != 0.) { // no need to adjust saturation if target color is black
1563         _saturationAdjustment->setValue( fround(tos - s, 4) );
1564     }
1565     _brightnessRange->setValue( ffloor(hsvmin.v, 4), fceil(hsvmax.v, 4) );
1566     _brightnessRangeRolloff->setValue( ffloor( (hsvmax.v - hsvmin.v) * DEFAULT_RECTANGLE_ROLLOFF, 4 ) );
1567     _brightnessAdjustment->setValue( fround(tov - v, 4) );
1568     endEditBlock();
1569 } // HSVToolPlugin::setSrcFromRectangle
1570 
1571 /* set up and run a processor */
1572 void
setSrcFromRectangleProcess(HueMeanProcessorBase & huemeanprocessor,HSVRangeProcessorBase & hsvrangeprocessor,const Image * srcImg,double,const OfxRectI & analysisWindow,double * hmean,HSVColor * hsvmin,HSVColor * hsvmax)1573 HSVToolPlugin::setSrcFromRectangleProcess(HueMeanProcessorBase &huemeanprocessor,
1574                                           HSVRangeProcessorBase &hsvrangeprocessor,
1575                                           const Image* srcImg,
1576                                           double /*time*/,
1577                                           const OfxRectI &analysisWindow,
1578                                           double *hmean,
1579                                           HSVColor *hsvmin,
1580                                           HSVColor *hsvmax)
1581 {
1582     // set the images
1583     huemeanprocessor.setDstImg( const_cast<Image*>(srcImg) ); // not a bug: we only set dst
1584 
1585     // set the render window
1586     huemeanprocessor.setRenderWindow(analysisWindow);
1587 
1588     // Call the base class process member, this will call the derived templated process code
1589     huemeanprocessor.process();
1590 
1591     if ( abort() ) {
1592         return;
1593     }
1594 
1595     *hmean = huemeanprocessor.getResult();
1596 
1597     // set the images
1598     hsvrangeprocessor.setDstImg( const_cast<Image*>(srcImg) ); // not a bug: we only set dst
1599 
1600     // set the render window
1601     hsvrangeprocessor.setRenderWindow(analysisWindow);
1602     hsvrangeprocessor.setHueMean(*hmean);
1603 
1604 
1605     // Call the base class process member, this will call the derived templated process code
1606     hsvrangeprocessor.process();
1607 
1608     if ( abort() ) {
1609         return;
1610     }
1611     hsvrangeprocessor.getResults(hsvmin, hsvmax);
1612 }
1613 
1614 void
changedParam(const InstanceChangedArgs & args,const std::string & paramName)1615 HSVToolPlugin::changedParam(const InstanceChangedArgs &args,
1616                             const std::string &paramName)
1617 {
1618     const double time = args.time;
1619 
1620     if ( (paramName == kParamSrcColor) && (args.reason == eChangeUserEdit) ) {
1621         // - when setting srcColor: compute hueRange, satRange, valRange (as empty ranges), set rolloffs to (50,0.3,0.3)
1622         double r, g, b;
1623         _srcColor->getValueAtTime(time, r, g, b);
1624         float h, s, v;
1625         Color::rgb_to_hsv( (float)r, (float)g, (float)b, &h, &s, &v );
1626         h *= 360. / OFXS_HUE_CIRCLE;
1627         double tor, tog, tob;
1628         _dstColor->getValueAtTime(time, tor, tog, tob);
1629         float toh, tos, tov;
1630         Color::rgb_to_hsv( (float)tor, (float)tog, (float)tob, &toh, &tos, &tov );
1631         toh *= 360. / OFXS_HUE_CIRCLE;
1632         double dh = normalizeAngleSigned(toh - h);
1633         beginEditBlock("setSrc");
1634         _hueRange->setValue(h, h);
1635         _hueRangeRolloff->setValue(50.);
1636         if (tov != 0.) { // no need to rotate if target color is black
1637             _hueRotation->setValue(dh);
1638         }
1639         _saturationRange->setValue(s, s);
1640         _saturationRangeRolloff->setValue(0.3);
1641         if (tov != 0.) { // no need to adjust saturation if target color is black
1642             _saturationAdjustment->setValue(tos - s);
1643         }
1644         _brightnessRange->setValue(v, v);
1645         _brightnessRangeRolloff->setValue(0.3);
1646         _brightnessAdjustment->setValue(tov - v);
1647         endEditBlock();
1648     } else if (paramName == kParamEnableRectangle) {
1649         // update visibility
1650         bool enableRectangle = _enableRectangle->getValueAtTime(time);
1651         _btmLeft->setIsSecretAndDisabled(!enableRectangle);
1652         _size->setIsSecretAndDisabled(!enableRectangle);
1653         _setSrcFromRectangle->setIsSecretAndDisabled(!enableRectangle);
1654         _srcColor->setEnabled(!enableRectangle);
1655     } else if ( (paramName == kParamSetSrcFromRectangle) && (args.reason == eChangeUserEdit) ) {
1656         auto_ptr<Image> src( ( _srcClip && _srcClip->isConnected() ) ?
1657                                   _srcClip->fetchImage(args.time) : 0 );
1658         if ( src.get() ) {
1659             if ( (src->getRenderScale().x != args.renderScale.x) ||
1660                  ( src->getRenderScale().y != args.renderScale.y) ) {
1661                 setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
1662                 throwSuiteStatusException(kOfxStatFailed);
1663             }
1664             OfxRectI analysisWindow;
1665             bool intersect = computeWindow(src.get(), args.time, &analysisWindow);
1666             if (intersect) {
1667 #             ifdef kOfxImageEffectPropInAnalysis // removed from OFX 1.4
1668                 getPropertySet().propSetInt(kOfxImageEffectPropInAnalysis, 1, false);
1669 #             endif
1670                 setSrcFromRectangle(src.get(), args.time, analysisWindow);
1671 #             ifdef kOfxImageEffectPropInAnalysis // removed from OFX 1.4
1672                 getPropertySet().propSetInt(kOfxImageEffectPropInAnalysis, 0, false);
1673 #             endif
1674             }
1675         }
1676     } else if ( (paramName == kParamDstColor) && (args.reason == eChangeUserEdit) ) {
1677         // - when setting dstColor: compute hueRotation, satAdjust and valAdjust
1678         double r, g, b;
1679         _srcColor->getValueAtTime(time, r, g, b);
1680         float h, s, v;
1681         Color::rgb_to_hsv( (float)r, (float)g, (float)b, &h, &s, &v );
1682         h *= 360. / OFXS_HUE_CIRCLE;
1683         double tor, tog, tob;
1684         _dstColor->getValueAtTime(time, tor, tog, tob);
1685         float toh, tos, tov;
1686         Color::rgb_to_hsv( (float)tor, (float)tog, (float)tob, &toh, &tos, &tov );
1687         toh *= 360. / OFXS_HUE_CIRCLE;
1688         double dh = normalizeAngleSigned(toh - h);
1689         beginEditBlock("setDst");
1690         if (tov != 0.) { // no need to adjust hue or saturation if target color is black
1691             _hueRotation->setValue(dh);
1692             _saturationAdjustment->setValue(tos - s);
1693         }
1694         _brightnessAdjustment->setValue(tov - v);
1695         endEditBlock();
1696     } else if ( (paramName == kParamPremult) && (args.reason == eChangeUserEdit) ) {
1697         _premultChanged->setValue(true);
1698     }
1699 } // HSVToolPlugin::changedParam
1700 
1701 void
changedClip(const InstanceChangedArgs & args,const std::string & clipName)1702 HSVToolPlugin::changedClip(const InstanceChangedArgs &args,
1703                            const std::string &clipName)
1704 {
1705     if ( (clipName == kOfxImageEffectSimpleSourceClipName) &&
1706          _srcClip && _srcClip->isConnected() &&
1707          !_premultChanged->getValue() &&
1708          ( args.reason == eChangeUserEdit) ) {
1709         if (_srcClip->getPixelComponents() != ePixelComponentRGBA) {
1710             _premult->setValue(false);
1711         } else {
1712             switch ( _srcClip->getPreMultiplication() ) {
1713             case eImageOpaque:
1714                 _premult->setValue(false);
1715                 break;
1716             case eImagePreMultiplied:
1717                 _premult->setValue(true);
1718                 break;
1719             case eImageUnPreMultiplied:
1720                 _premult->setValue(false);
1721                 break;
1722             }
1723         }
1724     }
1725 }
1726 
1727 /* Override the clip preferences */
1728 void
getClipPreferences(ClipPreferencesSetter & clipPreferences)1729 HSVToolPlugin::getClipPreferences(ClipPreferencesSetter &clipPreferences)
1730 {
1731     // set the components of _dstClip
1732     OutputAlphaEnum outputAlpha = (OutputAlphaEnum)_outputAlpha->getValue();
1733 
1734     if (outputAlpha != eOutputAlphaSource) {
1735         // output must be RGBA, output image is unpremult
1736         clipPreferences.setClipComponents(*_dstClip, ePixelComponentRGBA);
1737         clipPreferences.setClipComponents(*_srcClip, ePixelComponentRGBA);
1738         clipPreferences.setOutputPremultiplication(eImageUnPreMultiplied);
1739     }
1740 }
1741 
1742 class HSVToolInteract
1743     : public RectangleInteract
1744 {
1745 public:
1746 
HSVToolInteract(OfxInteractHandle handle,ImageEffect * effect)1747     HSVToolInteract(OfxInteractHandle handle,
1748                     ImageEffect* effect)
1749         : RectangleInteract(handle, effect)
1750         , _enableRectangle(NULL)
1751     {
1752         _enableRectangle = effect->fetchBooleanParam(kParamEnableRectangle);
1753         addParamToSlaveTo(_enableRectangle);
1754     }
1755 
1756 private:
1757 
1758     // overridden functions from Interact to do things
draw(const DrawArgs & args)1759     virtual bool draw(const DrawArgs &args) OVERRIDE FINAL
1760     {
1761         bool enableRectangle = _enableRectangle->getValueAtTime(args.time);
1762 
1763         if (enableRectangle) {
1764             return RectangleInteract::draw(args);
1765         }
1766 
1767         return false;
1768     }
1769 
penMotion(const PenArgs & args)1770     virtual bool penMotion(const PenArgs &args) OVERRIDE FINAL
1771     {
1772         bool enableRectangle = _enableRectangle->getValueAtTime(args.time);
1773 
1774         if (enableRectangle) {
1775             return RectangleInteract::penMotion(args);
1776         }
1777 
1778         return false;
1779     }
1780 
penDown(const PenArgs & args)1781     virtual bool penDown(const PenArgs &args) OVERRIDE FINAL
1782     {
1783         bool enableRectangle = _enableRectangle->getValueAtTime(args.time);
1784 
1785         if (enableRectangle) {
1786             return RectangleInteract::penDown(args);
1787         }
1788 
1789         return false;
1790     }
1791 
penUp(const PenArgs & args)1792     virtual bool penUp(const PenArgs &args) OVERRIDE FINAL
1793     {
1794         bool enableRectangle = _enableRectangle->getValueAtTime(args.time);
1795 
1796         if (enableRectangle) {
1797             return RectangleInteract::penUp(args);
1798         }
1799 
1800         return false;
1801     }
1802 
1803     //virtual bool keyDown(const KeyArgs &args) OVERRIDE;
1804     //virtual bool keyUp(const KeyArgs & args) OVERRIDE;
1805     //virtual void loseFocus(const FocusArgs &args) OVERRIDE FINAL;
1806 
1807 
1808     BooleanParam* _enableRectangle;
1809 };
1810 
1811 class HSVToolOverlayDescriptor
1812     : public DefaultEffectOverlayDescriptor<HSVToolOverlayDescriptor, HSVToolInteract>
1813 {
1814 };
1815 
1816 mDeclarePluginFactory(HSVToolPluginFactory, {ofxsThreadSuiteCheck();}, {});
1817 
1818 void
describe(ImageEffectDescriptor & desc)1819 HSVToolPluginFactory::describe(ImageEffectDescriptor &desc)
1820 {
1821     // basic labels
1822     desc.setLabel(kPluginName);
1823     desc.setPluginGrouping(kPluginGrouping);
1824     desc.setPluginDescription(kPluginDescription);
1825 
1826     desc.addSupportedContext(eContextFilter);
1827     desc.addSupportedContext(eContextGeneral);
1828     desc.addSupportedContext(eContextPaint);
1829     desc.addSupportedBitDepth(eBitDepthUByte);
1830     desc.addSupportedBitDepth(eBitDepthUShort);
1831     desc.addSupportedBitDepth(eBitDepthFloat);
1832 
1833     // set a few flags
1834     desc.setSingleInstance(false);
1835     desc.setHostFrameThreading(false);
1836     desc.setSupportsMultiResolution(kSupportsMultiResolution);
1837     desc.setSupportsTiles(kSupportsTiles);
1838     desc.setTemporalClipAccess(false);
1839     desc.setRenderTwiceAlways(false);
1840     desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
1841     desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);
1842     desc.setRenderThreadSafety(kRenderThreadSafety);
1843     desc.setOverlayInteractDescriptor(new HSVToolOverlayDescriptor);
1844 #ifdef OFX_EXTENSIONS_NATRON
1845     desc.setChannelSelector(ePixelComponentRGBA);
1846 #endif
1847 }
1848 
1849 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)1850 HSVToolPluginFactory::describeInContext(ImageEffectDescriptor &desc,
1851                                         ContextEnum context)
1852 {
1853     // Source clip only in the filter context
1854     // create the mandated source clip
1855     ClipDescriptor *srcClip = desc.defineClip(kOfxImageEffectSimpleSourceClipName);
1856 
1857     srcClip->addSupportedComponent(ePixelComponentRGBA);
1858     srcClip->addSupportedComponent(ePixelComponentRGB);
1859     srcClip->setTemporalClipAccess(false);
1860     srcClip->setSupportsTiles(kSupportsTiles);
1861     srcClip->setIsMask(false);
1862 
1863     // create the mandated output clip
1864     ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
1865     dstClip->addSupportedComponent(ePixelComponentRGBA);
1866     dstClip->addSupportedComponent(ePixelComponentRGB);
1867     dstClip->setSupportsTiles(kSupportsTiles);
1868 
1869     ClipDescriptor *maskClip = (context == eContextPaint) ? desc.defineClip("Brush") : desc.defineClip("Mask");
1870     maskClip->addSupportedComponent(ePixelComponentAlpha);
1871     maskClip->setTemporalClipAccess(false);
1872     if (context != eContextPaint) {
1873         maskClip->setOptional(true);
1874     }
1875     maskClip->setSupportsTiles(kSupportsTiles);
1876     maskClip->setIsMask(true);
1877 
1878     // make some pages and to things in
1879     PageParamDescriptor *page = desc.definePageParam("Controls");
1880 
1881     {
1882         GroupParamDescriptor *group = desc.defineGroupParam(kGroupColorReplacement);
1883         if (group) {
1884             group->setLabel(kGroupColorReplacementLabel);
1885             group->setHint(kGroupColorReplacementHint);
1886             group->setEnabled(true);
1887             if (page) {
1888                 page->addChild(*group);
1889             }
1890         }
1891 
1892         // enableRectangle
1893         {
1894             BooleanParamDescriptor *param = desc.defineBooleanParam(kParamEnableRectangle);
1895             param->setLabel(kParamEnableRectangleLabel);
1896             param->setHint(kParamEnableRectangleHint);
1897             param->setDefault(false);
1898             param->setAnimates(false);
1899             param->setEvaluateOnChange(false);
1900             if (group) {
1901                 param->setParent(*group);
1902             }
1903             if (page) {
1904                 page->addChild(*param);
1905             }
1906         }
1907 
1908         // btmLeft
1909         {
1910             Double2DParamDescriptor* param = desc.defineDouble2DParam(kParamRectangleInteractBtmLeft);
1911             param->setLabel(kParamRectangleInteractBtmLeftLabel);
1912             param->setDoubleType(eDoubleTypeXYAbsolute);
1913             if ( param->supportsDefaultCoordinateSystem() ) {
1914                 param->setDefaultCoordinateSystem(eCoordinatesNormalised); // no need of kParamDefaultsNormalised
1915             } else {
1916                 gHostSupportsDefaultCoordinateSystem = false; // no multithread here, see kParamDefaultsNormalised
1917             }
1918             param->setDefault(0.25, 0.25);
1919             param->setRange(-DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
1920             param->setDisplayRange(0, 0, 10000, 10000); // Resolve requires display range or values are clamped to (-1,1)
1921             param->setIncrement(1.);
1922             param->setHint(kParamRectangleInteractBtmLeftHint);
1923             param->setDigits(0);
1924             param->setEvaluateOnChange(false);
1925             param->setAnimates(true);
1926             if (group) {
1927                 param->setParent(*group);
1928             }
1929             if (page) {
1930                 page->addChild(*param);
1931             }
1932         }
1933 
1934         // size
1935         {
1936             Double2DParamDescriptor* param = desc.defineDouble2DParam(kParamRectangleInteractSize);
1937             param->setLabel(kParamRectangleInteractSizeLabel);
1938             param->setDoubleType(eDoubleTypeXY);
1939             if ( param->supportsDefaultCoordinateSystem() ) {
1940                 param->setDefaultCoordinateSystem(eCoordinatesNormalised); // no need of kParamDefaultsNormalised
1941             } else {
1942                 gHostSupportsDefaultCoordinateSystem = false; // no multithread here, see kParamDefaultsNormalised
1943             }
1944             param->setDefault(0.5, 0.5);
1945             param->setRange(0., 0., DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
1946             param->setDisplayRange(0, 0, 10000, 10000); // Resolve requires display range or values are clamped to (-1,1)
1947             param->setIncrement(1.);
1948             param->setDimensionLabels(kParamRectangleInteractSizeDim1, kParamRectangleInteractSizeDim2);
1949             param->setHint(kParamRectangleInteractSizeHint);
1950             param->setDigits(0);
1951             param->setEvaluateOnChange(false);
1952             param->setAnimates(true);
1953             if (group) {
1954                 param->setParent(*group);
1955             }
1956             if (page) {
1957                 page->addChild(*param);
1958             }
1959         }
1960         {
1961             PushButtonParamDescriptor *param = desc.definePushButtonParam(kParamSetSrcFromRectangle);
1962             param->setLabel(kParamSetSrcFromRectangleLabel);
1963             param->setHint(kParamSetSrcFromRectangleHint);
1964             if (group) {
1965                 param->setParent(*group);
1966             }
1967             if (page) {
1968                 page->addChild(*param);
1969             }
1970         }
1971         {
1972             RGBParamDescriptor *param = desc.defineRGBParam(kParamSrcColor);
1973             param->setLabel(kParamSrcColorLabel);
1974             param->setHint(kParamSrcColorHint);
1975             param->setEvaluateOnChange(false);
1976             if (group) {
1977                 param->setParent(*group);
1978             }
1979             if (page) {
1980                 page->addChild(*param);
1981             }
1982         }
1983         {
1984             RGBParamDescriptor *param = desc.defineRGBParam(kParamDstColor);
1985             param->setLabel(kParamDstColorLabel);
1986             param->setHint(kParamDstColorHint);
1987             param->setEvaluateOnChange(false);
1988             param->setLayoutHint(eLayoutHintDivider); // last parameter in the group
1989             if (group) {
1990                 param->setParent(*group);
1991             }
1992             if (page) {
1993                 page->addChild(*param);
1994             }
1995         }
1996     }
1997 
1998     {
1999         GroupParamDescriptor *group = desc.defineGroupParam(kGroupHue);
2000         if (group) {
2001             group->setLabel(kGroupHueLabel);
2002             group->setHint(kGroupHueHint);
2003             group->setEnabled(true);
2004             if (page) {
2005                 page->addChild(*group);
2006             }
2007         }
2008 
2009         {
2010             Double2DParamDescriptor *param = desc.defineDouble2DParam(kParamHueRange);
2011             param->setLabel(kParamHueRangeLabel);
2012             param->setHint(kParamHueRangeHint);
2013             param->setDimensionLabels("", ""); // the two values have the same meaning (they just define a range)
2014             param->setDefault(0., 360.);
2015             param->setRange(-DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
2016             param->setDisplayRange(0., 0., 360., 360.);
2017             param->setDoubleType(eDoubleTypeAngle);
2018             param->setUseHostNativeOverlayHandle(false);
2019             if (group) {
2020                 param->setParent(*group);
2021             }
2022             if (page) {
2023                 page->addChild(*param);
2024             }
2025         }
2026         {
2027             DoubleParamDescriptor *param = desc.defineDoubleParam(kParamHueRotation);
2028             param->setLabel(kParamHueRotationLabel);
2029             param->setHint(kParamHueRotationHint);
2030             param->setRange(-DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
2031             param->setDisplayRange(-180., 180.);
2032             param->setDoubleType(eDoubleTypeAngle);
2033             if (group) {
2034                 param->setParent(*group);
2035             }
2036             if (page) {
2037                 page->addChild(*param);
2038             }
2039         }
2040         {
2041             DoubleParamDescriptor *param = desc.defineDoubleParam(kParamHueRotationGain);
2042             param->setLabel(kParamHueRotationGainLabel);
2043             param->setHint(kParamHueRotationGainHint);
2044             param->setRange(-DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
2045             param->setDisplayRange(0., 2.);
2046             param->setDefault(1.);
2047             if (group) {
2048                 param->setParent(*group);
2049             }
2050             if (page) {
2051                 page->addChild(*param);
2052             }
2053         }
2054         {
2055             DoubleParamDescriptor *param = desc.defineDoubleParam(kParamHueRangeRolloff);
2056             param->setLabel(kParamHueRangeRolloffLabel);
2057             param->setHint(kParamHueRangeRolloffHint);
2058             param->setRange(0., 180.);
2059             param->setDisplayRange(0., 180.);
2060             param->setDoubleType(eDoubleTypeAngle);
2061             param->setLayoutHint(eLayoutHintDivider); // last parameter in the group
2062             if (group) {
2063                 param->setParent(*group);
2064             }
2065             if (page) {
2066                 page->addChild(*param);
2067             }
2068         }
2069     }
2070 
2071     {
2072         GroupParamDescriptor *group = desc.defineGroupParam(kGroupSaturation);
2073         if (group) {
2074             group->setLabel(kGroupSaturationLabel);
2075             group->setHint(kGroupSaturationHint);
2076             group->setEnabled(true);
2077             if (page) {
2078                 page->addChild(*group);
2079             }
2080         }
2081 
2082         {
2083             Double2DParamDescriptor *param = desc.defineDouble2DParam(kParamSaturationRange);
2084             param->setLabel(kParamSaturationRangeLabel);
2085             param->setHint(kParamSaturationRangeHint);
2086             param->setDimensionLabels("", ""); // the two values have the same meaning (they just define a range)
2087             param->setDefault(0., 1.);
2088             param->setDoubleType(eDoubleTypePlain);
2089             param->setRange(0., 0., 1., 1.);
2090             param->setDisplayRange(0., 0., 1, 1);
2091             param->setUseHostNativeOverlayHandle(false);
2092             if (group) {
2093                 param->setParent(*group);
2094             }
2095             if (page) {
2096                 page->addChild(*param);
2097             }
2098         }
2099         {
2100             DoubleParamDescriptor *param = desc.defineDoubleParam(kParamSaturationAdjustment);
2101             param->setLabel(kParamSaturationAdjustmentLabel);
2102             param->setHint(kParamSaturationAdjustmentHint);
2103             param->setRange(-1., 1.);
2104             param->setDisplayRange(0., 1.);
2105             if (group) {
2106                 param->setParent(*group);
2107             }
2108             if (page) {
2109                 page->addChild(*param);
2110             }
2111         }
2112         {
2113             DoubleParamDescriptor *param = desc.defineDoubleParam(kParamSaturationAdjustmentGain);
2114             param->setLabel(kParamSaturationAdjustmentGainLabel);
2115             param->setHint(kParamSaturationAdjustmentGainHint);
2116             param->setRange(-DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
2117             param->setDisplayRange(0., 2.);
2118             param->setDefault(1.);
2119             if (group) {
2120                 param->setParent(*group);
2121             }
2122             if (page) {
2123                 page->addChild(*param);
2124             }
2125         }
2126         {
2127             DoubleParamDescriptor *param = desc.defineDoubleParam(kParamSaturationRangeRolloff);
2128             param->setLabel(kParamSaturationRangeRolloffLabel);
2129             param->setHint(kParamSaturationRangeRolloffHint);
2130             param->setRange(0., 1.);
2131             param->setDisplayRange(0., 1.);
2132             param->setLayoutHint(eLayoutHintDivider); // last parameter in the group
2133             if (group) {
2134                 param->setParent(*group);
2135             }
2136             if (page) {
2137                 page->addChild(*param);
2138             }
2139         }
2140     }
2141 
2142     {
2143         GroupParamDescriptor *group = desc.defineGroupParam(kGroupBrightness);
2144         if (group) {
2145             group->setLabel(kGroupBrightnessLabel);
2146             group->setHint(kGroupBrightnessHint);
2147             group->setEnabled(true);
2148             if (page) {
2149                 page->addChild(*group);
2150             }
2151         }
2152 
2153         {
2154             Double2DParamDescriptor *param = desc.defineDouble2DParam(kParamBrightnessRange);
2155             param->setLabel(kParamBrightnessRangeLabel);
2156             param->setHint(kParamBrightnessRangeHint);
2157             param->setDimensionLabels("", ""); // the two values have the same meaning (they just define a range)
2158             param->setDefault(0., 1.);
2159             param->setDoubleType(eDoubleTypePlain);
2160             param->setRange(0., 0., DBL_MAX, DBL_MAX);
2161             param->setDisplayRange(0., 0., 1, 1);
2162             param->setUseHostNativeOverlayHandle(false);
2163             if (group) {
2164                 param->setParent(*group);
2165             }
2166             if (page) {
2167                 page->addChild(*param);
2168             }
2169         }
2170         {
2171             DoubleParamDescriptor *param = desc.defineDoubleParam(kParamBrightnessAdjustment);
2172             param->setLabel(kParamBrightnessAdjustmentLabel);
2173             param->setHint(kParamBrightnessAdjustmentHint);
2174             param->setRange(-DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
2175             param->setDisplayRange(0., 1.);
2176             if (group) {
2177                 param->setParent(*group);
2178             }
2179             if (page) {
2180                 page->addChild(*param);
2181             }
2182         }
2183         {
2184             DoubleParamDescriptor *param = desc.defineDoubleParam(kParamBrightnessAdjustmentGain);
2185             param->setLabel(kParamBrightnessAdjustmentGainLabel);
2186             param->setHint(kParamBrightnessAdjustmentGainHint);
2187             param->setRange(-DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
2188             param->setDisplayRange(0., 2.);
2189             param->setDefault(1.);
2190             if (group) {
2191                 param->setParent(*group);
2192             }
2193             if (page) {
2194                 page->addChild(*param);
2195             }
2196         }
2197         {
2198             DoubleParamDescriptor *param = desc.defineDoubleParam(kParamBrightnessRangeRolloff);
2199             param->setLabel(kParamBrightnessRangeRolloffLabel);
2200             param->setHint(kParamBrightnessRangeRolloffHint);
2201             param->setRange(0., DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
2202             param->setDisplayRange(0., 1.);
2203             param->setLayoutHint(eLayoutHintDivider); // last parameter in the group
2204             if (group) {
2205                 param->setParent(*group);
2206             }
2207             if (page) {
2208                 page->addChild(*param);
2209             }
2210         }
2211     }
2212 
2213     {
2214         BooleanParamDescriptor *param = desc.defineBooleanParam(kParamClampBlack);
2215         param->setLabel(kParamClampBlackLabel);
2216         param->setHint(kParamClampBlackHint);
2217         param->setDefault(true);
2218         param->setAnimates(true);
2219         param->setLayoutHint(eLayoutHintNoNewLine, 0);
2220         if (page) {
2221             page->addChild(*param);
2222         }
2223     }
2224     {
2225         BooleanParamDescriptor *param = desc.defineBooleanParam(kParamClampWhite);
2226         param->setLabel(kParamClampWhiteLabel);
2227         param->setHint(kParamClampWhiteHint);
2228         param->setDefault(false);
2229         param->setAnimates(true);
2230         if (page) {
2231             page->addChild(*param);
2232         }
2233     }
2234 
2235     {
2236         ChoiceParamDescriptor *param = desc.defineChoiceParam(kParamOutputAlpha);
2237         param->setLabel(kParamOutputAlphaLabel);
2238         param->setHint(kParamOutputAlphaHint);
2239         assert(param->getNOptions() == (int)eOutputAlphaSource);
2240         param->appendOption(kParamOutputAlphaOptionSource);
2241         assert(param->getNOptions() == (int)eOutputAlphaHue);
2242         param->appendOption(kParamOutputAlphaOptionHue);
2243         assert(param->getNOptions() == (int)eOutputAlphaSaturation);
2244         param->appendOption(kParamOutputAlphaOptionSaturation);
2245         assert(param->getNOptions() == (int)eOutputAlphaBrightness);
2246         param->appendOption(kParamOutputAlphaOptionBrightness);
2247         assert(param->getNOptions() == (int)eOutputAlphaHueSaturation);
2248         param->appendOption(kParamOutputAlphaOptionHueSaturation);
2249         assert(param->getNOptions() == (int)eOutputAlphaHueBrightness);
2250         param->appendOption(kParamOutputAlphaOptionHueBrightness);
2251         assert(param->getNOptions() == (int)eOutputAlphaSaturationBrightness);
2252         param->appendOption(kParamOutputAlphaOptionSaturationBrightness);
2253         assert(param->getNOptions() == (int)eOutputAlphaAll);
2254         param->appendOption(kParamOutputAlphaOptionAll);
2255         param->setDefault( (int)eOutputAlphaHue );
2256         param->setAnimates(false);
2257         desc.addClipPreferencesSlaveParam(*param);
2258         if (page) {
2259             page->addChild(*param);
2260         }
2261     }
2262 
2263     ofxsPremultDescribeParams(desc, page);
2264     ofxsMaskMixDescribeParams(desc, page);
2265 
2266     {
2267         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamPremultChanged);
2268         param->setDefault(false);
2269         param->setIsSecretAndDisabled(true);
2270         param->setAnimates(false);
2271         param->setEvaluateOnChange(false);
2272         if (page) {
2273             page->addChild(*param);
2274         }
2275     }
2276 
2277     // Some hosts (e.g. Resolve) may not support normalized defaults (setDefaultCoordinateSystem(eCoordinatesNormalised))
2278     if (!gHostSupportsDefaultCoordinateSystem) {
2279         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamDefaultsNormalised);
2280         param->setDefault(true);
2281         param->setEvaluateOnChange(false);
2282         param->setIsSecretAndDisabled(true);
2283         param->setIsPersistent(true);
2284         param->setAnimates(false);
2285         if (page) {
2286             page->addChild(*param);
2287         }
2288     }
2289 } // HSVToolPluginFactory::describeInContext
2290 
2291 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)2292 HSVToolPluginFactory::createInstance(OfxImageEffectHandle handle,
2293                                      ContextEnum /*context*/)
2294 {
2295     return new HSVToolPlugin(handle);
2296 }
2297 
2298 static HSVToolPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
2299 mRegisterPluginFactoryInstance(p)
2300 
2301 OFXS_NAMESPACE_ANONYMOUS_EXIT
2302