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 ¶mName) 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 ¶mName)
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