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 HueCorrect and HueKeyer plugins
21  */
22 
23 #include <cmath>
24 #include <cfloat> // DBL_MAX
25 #include <algorithm>
26 
27 #ifdef __APPLE__
28 #include <OpenGL/gl.h>
29 #else
30 #ifdef _WIN32
31 #define WIN32_LEAN_AND_MEAN
32 #ifndef NOMINMAX
33 #define NOMINMAX
34 #endif
35 #include <windows.h>
36 #endif
37 
38 #include <GL/gl.h>
39 #endif
40 
41 #include "ofxsProcessing.H"
42 #include "ofxsMaskMix.h"
43 #include "ofxsCoords.h"
44 #include "ofxsLut.h"
45 #include "ofxsMacros.h"
46 
47 using namespace OFX;
48 
49 OFXS_NAMESPACE_ANONYMOUS_ENTER
50 
51 #define kPluginName "HueCorrectOFX"
52 #define kPluginGrouping "Color"
53 #define kPluginDescription \
54     "Apply hue-dependent color adjustments using lookup curves.\n" \
55     "Hue and saturation are computed from the the source RGB values. Depending on the hue value, the various adjustment values are computed, and then applied:\n" \
56     "sat: saturation gain. This modification is applied last.\n" \
57     "lum: luminance gain\n" \
58     "red: red gain\n" \
59     "green: green gain\n" \
60     "blue: blue gain\n" \
61     "r_sup: red suppression. If r > min(g,b),  r = min(g,b) + r_sup * (r-min(g,b))\n" \
62     "g_sup: green suppression\n" \
63     "b_sup: blue suppression\n" \
64     "sat_thrsh: if source saturation is below this value, do not apply the lum, red, green, blue gains. Above this value, apply gain progressively.\n" \
65     "\n" \
66     "The 'Luminance Mix' parameter may be used to restore partially or fully the original luminance (luminance is computed using the 'Luminance Math' parameter).\n" \
67     "\n" \
68     "See also: http://opticalenquiry.com/nuke/index.php?title=HueCorrect"
69 
70 #define kPluginIdentifier "net.sf.openfx.HueCorrect"
71 #define kPluginVersionMajor 1 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
72 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
73 
74 #define kSupportsTiles 1
75 #define kSupportsMultiResolution 1
76 #define kSupportsRenderScale 1
77 #define kSupportsMultipleClipPARs false
78 #define kSupportsMultipleClipDepths false
79 #define kRenderThreadSafety eRenderFullySafe
80 
81 #define kParamHue "hue"
82 #define kParamHueLabel "Hue Curves"
83 #define kParamHueHint "Hue-dependent adjustment lookup curves:\n" \
84     "sat: saturation gain. This modification is applied last.\n" \
85     "lum: luminance gain\n" \
86     "red: red gain\n" \
87     "green: green gain\n" \
88     "blue: blue gain\n" \
89     "r_sup: red suppression. If r > min(g,b),  r = min(g,b) + r_sup * (r-min(g,b))\n" \
90     "g_sup: green suppression\n" \
91     "b_sup: blue suppression\n" \
92     "sat_thrsh: if source saturation is below this value, do not apply the lum, red, green, blue gains. Above this value, apply gain progressively."
93 
94 
95 #define kParamLuminanceMath "luminanceMath"
96 #define kParamLuminanceMathLabel "Luminance Math"
97 #define kParamLuminanceMathHint "Formula used to compute luminance from RGB values (only used by 'Set Master')."
98 #define kParamLuminanceMathOptionRec709 "Rec. 709", "Use Rec. 709 (0.2126r + 0.7152g + 0.0722b).", "rec709"
99 #define kParamLuminanceMathOptionRec2020 "Rec. 2020", "Use Rec. 2020 (0.2627r + 0.6780g + 0.0593b).", "rec2020"
100 #define kParamLuminanceMathOptionACESAP0 "ACES AP0", "Use ACES AP0 (0.3439664498r + 0.7281660966g + -0.0721325464b).", "acesap0"
101 #define kParamLuminanceMathOptionACESAP1 "ACES AP1", "Use ACES AP1 (0.2722287168r +  0.6740817658g +  0.0536895174b).", "acesap1"
102 #define kParamLuminanceMathOptionCcir601 "CCIR 601", "Use CCIR 601 (0.2989r + 0.5866g + 0.1145b).", "ccir601"
103 #define kParamLuminanceMathOptionAverage "Average", "Use average of r, g, b.", "average"
104 #define kParamLuminanceMathOptionMaximum "Max", "Use max or r, g, b.", "max"
105 
106 enum LuminanceMathEnum
107 {
108     eLuminanceMathRec709,
109     eLuminanceMathRec2020,
110     eLuminanceMathACESAP0,
111     eLuminanceMathACESAP1,
112     eLuminanceMathCcir601,
113     eLuminanceMathAverage,
114     eLuminanceMathMaximum,
115 };
116 
117 #define kParamClampBlack "clampBlack"
118 #define kParamClampBlackLabel "Clamp Black"
119 #define kParamClampBlackHint "All colors below 0 on output are set to 0."
120 
121 #define kParamClampWhite "clampWhite"
122 #define kParamClampWhiteLabel "Clamp White"
123 #define kParamClampWhiteHint "All colors above 1 on output are set to 1."
124 
125 #define kParamMixLuminanceEnable "mixLuminanceEnable"
126 #define kParamMixLuminanceEnableLabel "Mix Luminance"
127 #define kParamMixLuminanceEnableHint "Mix luminance"
128 
129 #define kParamMixLuminance "mixLuminance"
130 #define kParamMixLuminanceLabel ""
131 #define kParamMixLuminanceHint "Mix luminance"
132 
133 #define kParamPremultChanged "premultChanged"
134 
135 #define kCurveSat 0
136 #define kCurveLum 1
137 #define kCurveRed 2
138 #define kCurveGreen 3
139 #define kCurveBlue 4
140 #define kCurveRSup 5
141 #define kCurveGSup 6
142 #define kCurveBSup 7
143 #define kCurveSatThrsh 8
144 #define kCurveNb 9
145 
146 
147 class HueCorrectProcessorBase
148     : public ImageProcessor
149 {
150 protected:
151     const Image *_srcImg;
152     const Image *_maskImg;
153     bool _doMasking;
154     LuminanceMathEnum _luminanceMath;
155     double _luminanceMix;
156     const bool _clampBlack;
157     const bool _clampWhite;
158     bool _premult;
159     int _premultChannel;
160     double _mix;
161     bool _maskInvert;
162 
163 public:
HueCorrectProcessorBase(ImageEffect & instance,bool clampBlack,bool clampWhite)164     HueCorrectProcessorBase(ImageEffect &instance,
165                             bool clampBlack,
166                             bool clampWhite)
167         : ImageProcessor(instance)
168         , _srcImg(NULL)
169         , _maskImg(NULL)
170         , _doMasking(false)
171         , _luminanceMath(eLuminanceMathRec709)
172         , _luminanceMix(0.)
173         , _clampBlack(clampBlack)
174         , _clampWhite(clampWhite)
175         , _premult(false)
176         , _premultChannel(3)
177         , _mix(1.)
178         , _maskInvert(false)
179     {
180     }
181 
setSrcImg(const Image * v)182     void setSrcImg(const Image *v) {_srcImg = v; }
183 
setMaskImg(const Image * v,bool maskInvert)184     void setMaskImg(const Image *v,
185                     bool maskInvert) { _maskImg = v; _maskInvert = maskInvert; }
186 
doMasking(bool v)187     void doMasking(bool v) {_doMasking = v; }
188 
setValues(LuminanceMathEnum luminanceMath,double luminanceMix,bool premult,int premultChannel,double mix)189     void setValues(LuminanceMathEnum luminanceMath,
190                    double luminanceMix,
191                    bool premult,
192                    int premultChannel,
193                    double mix)
194     {
195         _luminanceMath = luminanceMath;
196         _luminanceMix = luminanceMix;
197         _premult = premult;
198         _premultChannel = premultChannel;
199         _mix = mix;
200     }
201 
202 protected:
203     // clamp for integer types
204     template<class PIX>
clamp(float value,int maxValue)205     float clamp(float value,
206                 int maxValue)
207     {
208         return std::max( 0.f, std::min( value, float(maxValue) ) );
209     }
210 
211     // clamp for integer types
212     template<class PIX>
clamp(double value,int maxValue)213     double clamp(double value,
214                  int maxValue)
215     {
216         return std::max( 0., std::min( value, double(maxValue) ) );
217     }
218 };
219 
220 
221 // floats don't clamp except if _clampBlack or _clampWhite
222 template<>
223 float
clamp(float value,int maxValue)224 HueCorrectProcessorBase::clamp<float>(float value,
225                                       int maxValue)
226 {
227     assert(maxValue == 1.);
228     if ( _clampBlack && (value < 0.) ) {
229         value = 0.f;
230     } else if ( _clampWhite && (value > 1.0) ) {
231         value = 1.0f;
232     }
233 
234     return value;
235 }
236 
237 template<typename T>
238 T
luminance(T r,T g,T b,LuminanceMathEnum luminanceMath)239 luminance(T r,
240           T g,
241           T b,
242           LuminanceMathEnum luminanceMath)
243 {
244     switch (luminanceMath) {
245     case eLuminanceMathRec709:
246     default:
247 
248         return Color::rgb709_to_y(r, g, b);
249 
250     case eLuminanceMathRec2020: // https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2087-0-201510-I!!PDF-E.pdf
251 
252         return Color::rgb2020_to_y(r, g, b);
253     case eLuminanceMathACESAP0: // https://en.wikipedia.org/wiki/Academy_Color_Encoding_System#Converting_ACES_RGB_values_to_CIE_XYZ_values
254 
255         return Color::rgbACESAP0_to_y(r, g, b);
256     case eLuminanceMathACESAP1: // https://en.wikipedia.org/wiki/Academy_Color_Encoding_System#Converting_ACES_RGB_values_to_CIE_XYZ_values
257 
258         return Color::rgbACESAP1_to_y(r, g, b);
259     case eLuminanceMathCcir601:
260 
261         return (T)(0.2989f * r + 0.5866f * g + 0.1145f * b);
262     case eLuminanceMathAverage:
263 
264         return (r + g + b) / 3;
265     case eLuminanceMathMaximum:
266 
267         return std::max(std::max(r, g), b);
268     }
269 }
270 
271 // template to do the processing.
272 // nbValues is the number of values in the LUT minus 1. For integer types, it should be the same as
273 // maxValue
274 template <class PIX, int nComponents, int maxValue, int nbValues>
275 class HueCorrectProcessor
276     : public HueCorrectProcessorBase
277 {
278 public:
279     // ctor
HueCorrectProcessor(ImageEffect & instance,const RenderArguments & args,ParametricParam * hueParam,bool clampBlack,bool clampWhite)280     HueCorrectProcessor(ImageEffect &instance,
281                         const RenderArguments &args,
282                         ParametricParam  *hueParam,
283                         bool clampBlack,
284                         bool clampWhite)
285         : HueCorrectProcessorBase(instance, clampBlack, clampWhite)
286         , _hueParam(hueParam)
287     {
288         // build the LUT
289         assert(_hueParam);
290         _time = args.time;
291         for (int c = 0; c < kCurveNb; ++c) {
292             _hue[c].resize(nbValues + 1);
293             for (int position = 0; position <= nbValues; ++position) {
294                 // position to evaluate the param at
295                 double parametricPos = 6 * double(position) / nbValues;
296 
297                 // evaluate the parametric param
298                 double value = _hueParam->getValue(c, _time, parametricPos);
299 
300                 // all the values (in HueCorrect) must be positive. We don't care if sat_thrsh goes above 1.
301                 value = std::max(0., value);
302                 // set that in the lut
303                 _hue[c][position] = value;
304             }
305         }
306     }
307 
308 private:
309     // and do some processing
multiThreadProcessImages(OfxRectI procWindow)310     void multiThreadProcessImages(OfxRectI procWindow)
311     {
312         assert(nComponents == 3 || nComponents == 4);
313         assert(_dstImg);
314         float tmpPix[4];
315         for (int y = procWindow.y1; y < procWindow.y2; y++) {
316             if ( _effect.abort() ) {
317                 break;
318             }
319 
320             PIX *dstPix = (PIX *) _dstImg->getPixelAddress(procWindow.x1, y);
321 
322             for (int x = procWindow.x1; x < procWindow.x2; x++) {
323                 const PIX *srcPix = (const PIX *)  (_srcImg ? _srcImg->getPixelAddress(x, y) : 0);
324                 float unpPix[4];
325                 ofxsUnPremult<PIX, nComponents, maxValue>(srcPix, unpPix, _premult, _premultChannel);
326                 // ofxsUnPremult outputs normalized data
327 
328                 float r = unpPix[0];
329                 float g = unpPix[1];
330                 float b = unpPix[2];
331                 float l_in = 0.;
332                 if (_luminanceMix > 0.) {
333                     l_in = luminance(r, g, b, _luminanceMath);
334                 }
335                 float h, s, v;
336                 Color::rgb_to_hsv( r, g, b, &h, &s, &v );
337                 h = h * 6 + 1;
338                 if (h > 6) {
339                     h -= 6;
340                 }
341                 double sat = interpolate(kCurveSat, h);
342                 double lum = interpolate(kCurveLum, h);
343                 double red = interpolate(kCurveRed, h);
344                 double green = interpolate(kCurveGreen, h);
345                 double blue = interpolate(kCurveBlue, h);
346                 double r_sup = interpolate(kCurveRSup, h);
347                 double g_sup = interpolate(kCurveGSup, h);
348                 double b_sup = interpolate(kCurveBSup, h);
349                 float sat_thrsh = interpolate(kCurveSatThrsh, h);
350 
351                 if (r_sup != 1.) {
352                     // If r > min(g,b),  r = min(g,b) + r_sup * (r-min(g,b))
353                     float m = std::min(g, b);
354                     if (r > m) {
355                         r = m + r_sup * (r - m);
356                     }
357                 }
358                 if (g_sup != 1.) {
359                     float m = std::min(r, b);
360                     if (g > m) {
361                         g = m + g_sup * (g - m);
362                     }
363                 }
364                 if (b_sup != 1.) {
365                     float m = std::min(r, g);
366                     if (b > m) {
367                         b = m + b_sup * (b - m);
368                     }
369                 }
370                 if (s > sat_thrsh) {
371                     // Get a smooth effect: identity at s=sat_thrsh, full if sat_thrsh = 0
372                     r *= (float)((sat_thrsh * 1. + (s - sat_thrsh) * red * lum) / s); // red * lum
373                     g *= (float)((sat_thrsh * 1. + (s - sat_thrsh) * green * lum) / s); // green * lum;
374                     b *= (float)((sat_thrsh * 1. + (s - sat_thrsh) * blue * lum) / s); // blue * lum;
375                 } else if (sat_thrsh == 0.) {
376                     assert(s == 0.);
377                     r *= (float)(red * lum); // red * lum
378                     g *= (float)(green * lum); // green * lum;
379                     b *= (float)(blue * lum); // blue * lum;
380                 }
381                 if (sat != 1.) {
382                     float l_sat = luminance(r, g, b, _luminanceMath);
383                     r = (float)((1. - sat) * l_sat + sat * r);
384                     g = (float)((1. - sat) * l_sat + sat * g);
385                     b = (float)((1. - sat) * l_sat + sat * b);
386                 }
387                 if (_luminanceMix > 0.) {
388                     float l_out = luminance(r, g, b, _luminanceMath);
389                     if (l_out <= 0.) {
390                         r = g = b = l_in;
391                     } else {
392                         float f = (float)(1 + _luminanceMix * (l_in / l_out - 1.));
393                         r *= f;
394                         g *= f;
395                         b *= f;
396                     }
397                 }
398 
399                 tmpPix[0] = clamp<float>(r, 1.);
400                 tmpPix[1] = clamp<float>(g, 1.);
401                 tmpPix[2] = clamp<float>(b, 1.);
402                 tmpPix[3] = unpPix[3]; // alpha is left unchanged
403                 for (int c = 0; c < nComponents; ++c) {
404                     assert( !OFX::IsNaN(unpPix[c]) && !OFX::IsNaN(unpPix[c]) &&
405                             !OFX::IsNaN(tmpPix[c]) && !OFX::IsNaN(tmpPix[c]) );
406                 }
407 
408                 // ofxsPremultMaskMixPix expects normalized input
409                 ofxsPremultMaskMixPix<PIX, nComponents, maxValue, true>(tmpPix, _premult, _premultChannel, x, y, srcPix, _doMasking, _maskImg, (float)_mix, _maskInvert, dstPix);
410                 // increment the dst pixel
411                 dstPix += nComponents;
412             }
413         }
414     } // multiThreadProcessImages
415 
interpolate(int c,double value)416     double interpolate(int c, // the curve number
417                        double value)
418     {
419         if ( (value < 0.) || (6. < value) ) {
420             // slow version
421             double ret = _hueParam->getValue(c, _time, value);
422 
423             return ret;
424         } else {
425             double x = value / 6.;
426             int i = (int)(x * nbValues);
427             assert(0 <= i && i <= nbValues);
428             double alpha = std::max( 0., std::min(x * nbValues - i, 1.) );
429             double a = _hue[c][i];
430             double b = (i  < nbValues) ? _hue[c][i + 1] : 0.f;
431 
432             return a * (1.f - alpha) + b * alpha;
433         }
434     }
435 
436 private:
437     std::vector<double> _hue[kCurveNb];
438     ParametricParam*  _hueParam;
439     double _time;
440 };
441 
442 
443 ////////////////////////////////////////////////////////////////////////////////
444 /** @brief The plugin that does our work */
445 class HueCorrectPlugin
446     : public ImageEffect
447 {
448 public:
HueCorrectPlugin(OfxImageEffectHandle handle)449     HueCorrectPlugin(OfxImageEffectHandle handle)
450         : ImageEffect(handle)
451         , _dstClip(NULL)
452         , _srcClip(NULL)
453         , _maskClip(NULL)
454         , _luminanceMath(NULL)
455         , _premultChanged(NULL)
456     {
457         _dstClip = fetchClip(kOfxImageEffectOutputClipName);
458         assert( _dstClip && (!_dstClip->isConnected() || _dstClip->getPixelComponents() == ePixelComponentAlpha ||
459                              _dstClip->getPixelComponents() == ePixelComponentRGB ||
460                              _dstClip->getPixelComponents() == ePixelComponentRGBA) );
461         _srcClip = getContext() == eContextGenerator ? NULL : fetchClip(kOfxImageEffectSimpleSourceClipName);
462         assert( (!_srcClip && getContext() == eContextGenerator) ||
463                 ( _srcClip && (!_srcClip->isConnected() || _srcClip->getPixelComponents() ==  ePixelComponentAlpha ||
464                                _srcClip->getPixelComponents() == ePixelComponentRGB ||
465                                _srcClip->getPixelComponents() == ePixelComponentRGBA) ) );
466 
467         _maskClip = fetchClip(getContext() == eContextPaint ? "Brush" : "Mask");
468         assert(!_maskClip || !_maskClip->isConnected() || _maskClip->getPixelComponents() == ePixelComponentAlpha);
469         _hue = fetchParametricParam(kParamHue);
470         _luminanceMath = fetchChoiceParam(kParamLuminanceMath);
471         _luminanceMixEnable = fetchBooleanParam(kParamMixLuminanceEnable);
472         _luminanceMix = fetchDoubleParam(kParamMixLuminance);
473         assert(_luminanceMath);
474         _clampBlack = fetchBooleanParam(kParamClampBlack);
475         _clampWhite = fetchBooleanParam(kParamClampWhite);
476         assert(_clampBlack && _clampWhite);
477         _premult = fetchBooleanParam(kParamPremult);
478         _premultChannel = fetchChoiceParam(kParamPremultChannel);
479         assert(_premult && _premultChannel);
480         _mix = fetchDoubleParam(kParamMix);
481         _maskApply = ( ofxsMaskIsAlwaysConnected( OFX::getImageEffectHostDescription() ) && paramExists(kParamMaskApply) ) ? fetchBooleanParam(kParamMaskApply) : 0;
482         _maskInvert = fetchBooleanParam(kParamMaskInvert);
483         assert(_mix && _maskInvert);
484         _premultChanged = fetchBooleanParam(kParamPremultChanged);
485         assert(_premultChanged);
486         _luminanceMix->setEnabled( _luminanceMixEnable->getValue() );
487     }
488 
489 private:
490     virtual void render(const RenderArguments &args) OVERRIDE FINAL;
491     virtual bool isIdentity(const IsIdentityArguments &args, Clip * &identityClip, double &identityTime, int& view, std::string& plane) OVERRIDE FINAL;
492 
493     /** @brief called when a clip has just been changed in some way (a rewire maybe) */
494     virtual void changedClip(const InstanceChangedArgs &args, const std::string &clipName) OVERRIDE FINAL;
495 
496     template <int nComponents>
497     void renderForComponents(const RenderArguments &args, BitDepthEnum dstBitDepth);
498 
499     void setupAndProcess(HueCorrectProcessorBase &, const RenderArguments &args);
500 
changedParam(const InstanceChangedArgs & args,const std::string & paramName)501     virtual void changedParam(const InstanceChangedArgs &args,
502                               const std::string &paramName) OVERRIDE FINAL
503     {
504         const double time = args.time;
505 
506         if ( (paramName == kParamPremult) && (args.reason == eChangeUserEdit) ) {
507             _premultChanged->setValue(true);
508         }
509         if ( (paramName == kParamMixLuminanceEnable) && (args.reason == eChangeUserEdit) ) {
510             _luminanceMix->setEnabled( _luminanceMixEnable->getValueAtTime(time) );
511         }
512     } // changedParam
513 
514 private:
515     Clip *_dstClip;
516     Clip *_srcClip;
517     Clip *_maskClip;
518     ParametricParam  *_hue;
519     ChoiceParam* _luminanceMath;
520     BooleanParam* _luminanceMixEnable;
521     DoubleParam* _luminanceMix;
522     BooleanParam* _clampBlack;
523     BooleanParam* _clampWhite;
524     BooleanParam* _premult;
525     ChoiceParam* _premultChannel;
526     DoubleParam* _mix;
527     BooleanParam* _maskApply;
528     BooleanParam* _maskInvert;
529     BooleanParam* _premultChanged; // set to true the first time the user connects src
530 };
531 
532 
533 void
setupAndProcess(HueCorrectProcessorBase & processor,const RenderArguments & args)534 HueCorrectPlugin::setupAndProcess(HueCorrectProcessorBase &processor,
535                                   const RenderArguments &args)
536 {
537     const double time = args.time;
538 
539     assert(_dstClip);
540     auto_ptr<Image> dst( _dstClip->fetchImage(time) );
541     if ( !dst.get() ) {
542         throwSuiteStatusException(kOfxStatFailed);
543     }
544     BitDepthEnum dstBitDepth    = dst->getPixelDepth();
545     PixelComponentEnum dstComponents  = dst->getPixelComponents();
546     if ( ( dstBitDepth != _dstClip->getPixelDepth() ) ||
547          ( dstComponents != _dstClip->getPixelComponents() ) ) {
548         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong depth or components");
549         throwSuiteStatusException(kOfxStatFailed);
550     }
551     if ( (dst->getRenderScale().x != args.renderScale.x) ||
552          ( dst->getRenderScale().y != args.renderScale.y) ||
553          ( ( dst->getField() != eFieldNone) /* for DaVinci Resolve */ && ( dst->getField() != args.fieldToRender) ) ) {
554         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
555         throwSuiteStatusException(kOfxStatFailed);
556     }
557     auto_ptr<const Image> src( ( _srcClip && _srcClip->isConnected() ) ?
558                                     _srcClip->fetchImage(time) : 0 );
559     if ( src.get() ) {
560         if ( (src->getRenderScale().x != args.renderScale.x) ||
561              ( src->getRenderScale().y != args.renderScale.y) ||
562              ( ( src->getField() != eFieldNone) /* for DaVinci Resolve */ && ( src->getField() != args.fieldToRender) ) ) {
563             setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
564             throwSuiteStatusException(kOfxStatFailed);
565         }
566         BitDepthEnum srcBitDepth      = src->getPixelDepth();
567         PixelComponentEnum srcComponents = src->getPixelComponents();
568         if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
569             throwSuiteStatusException(kOfxStatErrImageFormat);
570         }
571     }
572     bool doMasking = ( ( !_maskApply || _maskApply->getValueAtTime(time) ) && _maskClip && _maskClip->isConnected() );
573     auto_ptr<const Image> mask(doMasking ? _maskClip->fetchImage(time) : 0);
574     if (doMasking) {
575         if ( mask.get() ) {
576             if ( (mask->getRenderScale().x != args.renderScale.x) ||
577                  ( mask->getRenderScale().y != args.renderScale.y) ||
578                  ( ( mask->getField() != eFieldNone) /* for DaVinci Resolve */ && ( mask->getField() != args.fieldToRender) ) ) {
579                 setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
580                 throwSuiteStatusException(kOfxStatFailed);
581             }
582         }
583         bool maskInvert;
584         _maskInvert->getValueAtTime(time, maskInvert);
585         processor.doMasking(true);
586         processor.setMaskImg(mask.get(), maskInvert);
587     }
588 
589     if ( src.get() && dst.get() ) {
590         BitDepthEnum srcBitDepth      = src->getPixelDepth();
591         PixelComponentEnum srcComponents = src->getPixelComponents();
592         BitDepthEnum dstBitDepth       = dst->getPixelDepth();
593         PixelComponentEnum dstComponents  = dst->getPixelComponents();
594 
595         // see if they have the same depths and bytes and all
596         if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
597             throwSuiteStatusException(kOfxStatErrImageFormat);
598         }
599     }
600 
601     processor.setDstImg( dst.get() );
602     processor.setSrcImg( src.get() );
603     processor.setRenderWindow(args.renderWindow);
604     LuminanceMathEnum luminanceMath = (LuminanceMathEnum)_luminanceMath->getValueAtTime(time);
605     bool luminanceMixEnable = _luminanceMixEnable->getValueAtTime(time);
606     double luminanceMix = luminanceMixEnable ? _luminanceMix->getValueAtTime(time) : 0;
607     bool premult = _premult->getValueAtTime(time);
608     int premultChannel = _premultChannel->getValueAtTime(time);
609     double mix = _mix->getValueAtTime(time);
610     processor.setValues(luminanceMath, luminanceMix, premult, premultChannel, mix);
611     processor.process();
612 } // HueCorrectPlugin::setupAndProcess
613 
614 // the internal render function
615 template <int nComponents>
616 void
renderForComponents(const RenderArguments & args,BitDepthEnum dstBitDepth)617 HueCorrectPlugin::renderForComponents(const RenderArguments &args,
618                                       BitDepthEnum dstBitDepth)
619 {
620     const double time = args.time;
621     bool clampBlack = _clampBlack->getValueAtTime(time);
622     bool clampWhite = _clampWhite->getValueAtTime(time);
623 
624     switch (dstBitDepth) {
625     case eBitDepthUByte: {
626         HueCorrectProcessor<unsigned char, nComponents, 255, 255> fred(*this, args, _hue, clampBlack, clampWhite);
627         setupAndProcess(fred, args);
628         break;
629     }
630     case eBitDepthUShort: {
631         HueCorrectProcessor<unsigned short, nComponents, 65535, 65535> fred(*this, args, _hue, clampBlack, clampWhite);
632         setupAndProcess(fred, args);
633         break;
634     }
635     case eBitDepthFloat: {
636         HueCorrectProcessor<float, nComponents, 1, 1023> fred(*this, args, _hue, clampBlack, clampWhite);
637         setupAndProcess(fred, args);
638         break;
639     }
640     default:
641         throwSuiteStatusException(kOfxStatErrUnsupported);
642     }
643 }
644 
645 void
render(const RenderArguments & args)646 HueCorrectPlugin::render(const RenderArguments &args)
647 {
648     BitDepthEnum dstBitDepth    = _dstClip->getPixelDepth();
649     PixelComponentEnum dstComponents  = _dstClip->getPixelComponents();
650 
651     assert( kSupportsMultipleClipPARs   || !_srcClip || _srcClip->getPixelAspectRatio() == _dstClip->getPixelAspectRatio() );
652     assert( kSupportsMultipleClipDepths || !_srcClip || _srcClip->getPixelDepth()       == _dstClip->getPixelDepth() );
653     if (dstComponents == ePixelComponentRGBA) {
654         renderForComponents<4>(args, dstBitDepth);
655     } else if (dstComponents == ePixelComponentRGB) {
656         renderForComponents<3>(args, dstBitDepth);
657 #ifdef OFX_EXTENSIONS_NATRON
658         //} else if (dstComponents == ePixelComponentXY) {
659         //    renderForComponents<2>(args, dstBitDepth);
660 #endif
661         //} else {
662         //    assert(dstComponents == ePixelComponentAlpha);
663         //    renderForComponents<1>(args, dstBitDepth);
664     } else {
665         throwSuiteStatusException(kOfxStatErrFormat);
666     }
667 }
668 
669 bool
isIdentity(const IsIdentityArguments & args,Clip * & identityClip,double &,int &,std::string &)670 HueCorrectPlugin::isIdentity(const IsIdentityArguments &args,
671                              Clip * &identityClip,
672                              double & /*identityTime*/
673                              , int& /*view*/, std::string& /*plane*/)
674 {
675     const double time = args.time;
676     bool doMasking = ( ( !_maskApply || _maskApply->getValueAtTime(time) ) && _maskClip && _maskClip->isConnected() );
677 
678     if (doMasking) {
679         bool maskInvert;
680         _maskInvert->getValueAtTime(time, maskInvert);
681         if (!maskInvert) {
682             OfxRectI maskRoD;
683             if (getImageEffectHostDescription()->supportsMultiResolution) {
684                 // In Sony Catalyst Edit, clipGetRegionOfDefinition returns the RoD in pixels instead of canonical coordinates.
685                 // In hosts that do not support multiResolution (e.g. Sony Catalyst Edit), all inputs have the same RoD anyway.
686                 Coords::toPixelEnclosing(_maskClip->getRegionOfDefinition(time), args.renderScale, _maskClip->getPixelAspectRatio(), &maskRoD);
687                 // effect is identity if the renderWindow doesn't intersect the mask RoD
688                 if ( !Coords::rectIntersection<OfxRectI>(args.renderWindow, maskRoD, 0) ) {
689                     identityClip = _srcClip;
690 
691                     return true;
692                 }
693             }
694         }
695     }
696 
697     return false;
698 }
699 
700 void
changedClip(const InstanceChangedArgs & args,const std::string & clipName)701 HueCorrectPlugin::changedClip(const InstanceChangedArgs &args,
702                               const std::string &clipName)
703 {
704     if ( (clipName == kOfxImageEffectSimpleSourceClipName) &&
705          _srcClip && _srcClip->isConnected() &&
706          !_premultChanged->getValue() &&
707          ( args.reason == eChangeUserEdit) ) {
708         if (_srcClip->getPixelComponents() != ePixelComponentRGBA) {
709             _premult->setValue(false);
710         } else {
711             switch ( _srcClip->getPreMultiplication() ) {
712             case eImageOpaque:
713                 _premult->setValue(false);
714                 break;
715             case eImagePreMultiplied:
716                 _premult->setValue(true);
717                 break;
718             case eImageUnPreMultiplied:
719                 _premult->setValue(false);
720                 break;
721             }
722         }
723     }
724 }
725 
726 class HueCorrectInteract
727     : public ParamInteract
728 {
729 public:
HueCorrectInteract(OfxInteractHandle handle,ImageEffect * effect,const std::string & paramName)730     HueCorrectInteract(OfxInteractHandle handle,
731                        ImageEffect* effect,
732                        const std::string& paramName)
733         : ParamInteract(handle, effect)
734         , _hueParam(NULL)
735         , _xMin(0.)
736         , _xMax(0.)
737         , _yMin(0.)
738         , _yMax(0.)
739     {
740         _hueParam = effect->fetchParametricParam(paramName);
741         setColourPicking(true);
742         _hueParam->getRange(_xMin, _xMax);
743         _hueParam->getDimensionDisplayRange(0, _yMin, _yMax);
744         if ( (_yMin == 0.) && (_yMax == 0.) ) {
745             _yMax = 2.; // default for hosts that don't support displayrange
746         }
747     }
748 
draw(const DrawArgs & args)749     virtual bool draw(const DrawArgs &args) OVERRIDE FINAL
750     {
751         //const double time = args.time;
752 
753         // let us draw one slice every 8 pixels
754         const int sliceWidth = 8;
755         const float s = 1.;
756         const float v = 0.3;
757         int nbValues = args.pixelScale.x > 0 ? std::ceil( (_xMax - _xMin) / (sliceWidth * args.pixelScale.x) ) : 1;
758 
759         glBegin (GL_TRIANGLE_STRIP);
760 
761         for (int position = 0; position <= nbValues; ++position) {
762             // position to evaluate the param at
763             double parametricPos = _xMin + (_xMax - _xMin) * double(position) / nbValues;
764 
765             // red is at parametricPos = 1
766             float h = (parametricPos - 1) / 6;
767             float r, g, b;
768             Color::hsv_to_rgb( h, s, v, &r, &g, &b );
769             glColor3f(r, g, b);
770             glVertex2f(parametricPos, _yMin);
771             glVertex2f(parametricPos, _yMax);
772         }
773 
774         glEnd();
775 
776         if (args.hasPickerColour) {
777             glLineWidth(1.5);
778             glBegin(GL_LINES);
779             {
780                 float h, s, v;
781                 Color::rgb_to_hsv( args.pickerColour.r, args.pickerColour.g, args.pickerColour.b, &h, &s, &v );
782                 const OfxRGBColourD yellow   = {1, 1, 0};
783                 const OfxRGBColourD grey  = {2. / 3., 2. / 3., 2. / 3.};
784                 glColor3f(yellow.r, yellow.g, yellow.b);
785                 // map [0,1] to [0,6]
786                 h = _xMin + h * (_xMax - _xMin) + 1;
787                 if (h > 6) {
788                     h -= 6;
789                 }
790                 glVertex2f(h, _yMin);
791                 glVertex2f(h, _yMax);
792                 glColor3f(grey.r, grey.g, grey.b);
793                 glVertex2f(_xMin, s);
794                 glVertex2f(_xMax, s);
795             }
796             glEnd();
797         }
798 
799         return true;
800     } // draw
801 
~HueCorrectInteract()802     virtual ~HueCorrectInteract() {}
803 
804 protected:
805     ParametricParam* _hueParam;
806     double _xMin, _xMax;
807     double _yMin, _yMax;
808 };
809 
810 // We are lucky, there's only one hue param, so we need only one interact
811 // descriptor. If there were several, be would have to use a template parameter,
812 // as in propTester.cpp
813 class HueCorrectInteractDescriptor
814     : public DefaultParamInteractDescriptor<HueCorrectInteractDescriptor, HueCorrectInteract>
815 {
816 public:
describe()817     virtual void describe() OVERRIDE FINAL
818     {
819         setColourPicking(true);
820     }
821 };
822 
823 mDeclarePluginFactory(HueCorrectPluginFactory, {ofxsThreadSuiteCheck();}, {});
824 
825 void
describe(ImageEffectDescriptor & desc)826 HueCorrectPluginFactory::describe(ImageEffectDescriptor &desc)
827 {
828     desc.setLabel(kPluginName);
829     desc.setPluginGrouping(kPluginGrouping);
830     desc.setPluginDescription(kPluginDescription);
831 
832     desc.addSupportedContext(eContextFilter);
833     desc.addSupportedContext(eContextPaint);
834     desc.addSupportedContext(eContextGeneral);
835     desc.addSupportedBitDepth(eBitDepthUByte);
836     desc.addSupportedBitDepth(eBitDepthUShort);
837     desc.addSupportedBitDepth(eBitDepthFloat);
838 
839     desc.setSingleInstance(false);
840     desc.setHostFrameThreading(false);
841     desc.setSupportsMultiResolution(kSupportsMultiResolution);
842     desc.setSupportsTiles(kSupportsTiles);
843     desc.setTemporalClipAccess(false);
844     desc.setRenderTwiceAlways(false);
845     desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
846     desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);
847     desc.setRenderThreadSafety(kRenderThreadSafety);
848     // returning an error here crashes Nuke
849     //if (!getImageEffectHostDescription()->supportsParametricParameter) {
850     //  throwHostMissingSuiteException(kOfxParametricParameterSuite);
851     //}
852 #ifdef OFX_EXTENSIONS_NATRON
853     desc.setChannelSelector(ePixelComponentRGB);
854 #endif
855 }
856 
857 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)858 HueCorrectPluginFactory::describeInContext(ImageEffectDescriptor &desc,
859                                            ContextEnum context)
860 {
861     const ImageEffectHostDescription &gHostDescription = *getImageEffectHostDescription();
862     const bool supportsParametricParameter = ( gHostDescription.supportsParametricParameter &&
863                                                !(gHostDescription.hostName == "uk.co.thefoundry.nuke" &&
864                                                  8 <= gHostDescription.versionMajor && gHostDescription.versionMajor <= 10) );  // Nuke 8-10 are known to *not* support Parametric
865 
866     if (!supportsParametricParameter) {
867         throwHostMissingSuiteException(kOfxParametricParameterSuite);
868     }
869 
870     ClipDescriptor *srcClip = desc.defineClip(kOfxImageEffectSimpleSourceClipName);
871     assert(srcClip);
872     srcClip->addSupportedComponent(ePixelComponentRGBA);
873     srcClip->addSupportedComponent(ePixelComponentRGB);
874 #ifdef OFX_EXTENSIONS_NATRON
875     //srcClip->addSupportedComponent(ePixelComponentXY);
876 #endif
877     //srcClip->addSupportedComponent(ePixelComponentAlpha);
878     srcClip->setTemporalClipAccess(false);
879     srcClip->setSupportsTiles(kSupportsTiles);
880     srcClip->setIsMask(false);
881 
882     ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
883     assert(dstClip);
884     dstClip->addSupportedComponent(ePixelComponentRGBA);
885     dstClip->addSupportedComponent(ePixelComponentRGB);
886 #ifdef OFX_EXTENSIONS_NATRON
887     //dstClip->addSupportedComponent(ePixelComponentXY);
888 #endif
889     //dstClip->addSupportedComponent(ePixelComponentAlpha);
890     dstClip->setSupportsTiles(kSupportsTiles);
891 
892     ClipDescriptor *maskClip = (context == eContextPaint) ? desc.defineClip("Brush") : desc.defineClip("Mask");
893     maskClip->addSupportedComponent(ePixelComponentAlpha);
894     maskClip->setTemporalClipAccess(false);
895     if (context != eContextPaint) {
896         maskClip->setOptional(true);
897     }
898     maskClip->setSupportsTiles(kSupportsTiles);
899     maskClip->setIsMask(true);
900 
901     // make some pages and to things in
902     PageParamDescriptor *page = desc.definePageParam("Controls");
903 
904     // define it
905     {
906         ParametricParamDescriptor* param = desc.defineParametricParam(kParamHue);
907         assert(param);
908         param->setPeriodic(true);
909         param->setLabel(kParamHueLabel);
910         param->setHint(kParamHueHint);
911         {
912             HueCorrectInteractDescriptor* interact = new HueCorrectInteractDescriptor;
913             param->setInteractDescriptor(interact);
914         }
915 
916         // define it as three dimensional
917         param->setDimension(kCurveNb);
918 
919         // label our dimensions are r/g/b
920         param->setDimensionLabel("sat", kCurveSat);
921         param->setDimensionLabel("lum", kCurveLum);
922         param->setDimensionLabel("red", kCurveRed);
923         param->setDimensionLabel("green", kCurveGreen);
924         param->setDimensionLabel("blue", kCurveBlue);
925         param->setDimensionLabel("r_sup", kCurveRSup);
926         param->setDimensionLabel("g_sup", kCurveGSup);
927         param->setDimensionLabel("b_sup", kCurveBSup);
928         param->setDimensionLabel("sat_thrsh", kCurveSatThrsh);
929 
930         // set the UI colour for each dimension
931         //const OfxRGBColourD master  = {0.9, 0.9, 0.9};
932         // the following are magic colors, they all have the same Rec709 luminance
933         const OfxRGBColourD red   = {0.711519527404004, 0.164533420851110, 0.164533420851110};      //set red color to red curve
934         const OfxRGBColourD green = {0., 0.546986106552894, 0.};        //set green color to green curve
935         const OfxRGBColourD blue  = {0.288480472595996, 0.288480472595996, 0.835466579148890};      //set blue color to blue curve
936         const OfxRGBColourD alpha  = {0.398979, 0.398979, 0.398979};
937         param->setUIColour( kCurveSat, alpha );
938         param->setUIColour( kCurveLum, alpha );
939         param->setUIColour( kCurveRed, red );
940         param->setUIColour( kCurveGreen, green );
941         param->setUIColour( kCurveBlue, blue );
942         param->setUIColour( kCurveRSup, red );
943         param->setUIColour( kCurveGSup, green );
944         param->setUIColour( kCurveBSup, blue );
945         param->setUIColour( kCurveSatThrsh, alpha );
946 
947         // set the min/max parametric range to 0..6
948         param->setRange(0.0, 6.0);
949         // set the default Y range to 0..1 for all dimensions
950         param->setDimensionDisplayRange(0., 1., kCurveSat);
951         param->setDimensionDisplayRange(0., 1., kCurveLum);
952         param->setDimensionDisplayRange(0., 1., kCurveRed);
953         param->setDimensionDisplayRange(0., 1., kCurveGreen);
954         param->setDimensionDisplayRange(0., 1., kCurveBlue);
955         param->setDimensionDisplayRange(0., 1., kCurveRSup);
956         param->setDimensionDisplayRange(0., 1., kCurveGSup);
957         param->setDimensionDisplayRange(0., 1., kCurveBSup);
958         param->setDimensionDisplayRange(0., 1., kCurveSatThrsh);
959 
960 
961         int plast = param->supportsPeriodic() ? 5 : 6;
962         // set a default curve
963         for (int c = 0; c < kCurveNb; ++c) {
964             // minimum/maximum: are these supported by OpenFX?
965             param->setDimensionRange(0., c == kCurveSatThrsh ? 1. : DBL_MAX, c);
966             param->setDimensionDisplayRange(0., 2., c);
967             for (int p = 0; p <= plast; ++p) {
968                 // add a control point at p
969                 param->addControlPoint(c, // curve to set
970                                        0.0,   // time, ignored in this case, as we are not adding a key
971                                        p,   // parametric position, zero
972                                        (c == kCurveSatThrsh) ? 0. : 1.,   // value to be
973                                        false);   // don't add a key
974             }
975         }
976 
977         if (page) {
978             page->addChild(*param);
979         }
980     }
981     {
982         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamLuminanceMath);
983         param->setLabel(kParamLuminanceMathLabel);
984         param->setHint(kParamLuminanceMathHint);
985         param->setEvaluateOnChange(false); // WARNING: RENDER IS NOT AFFECTED BY THIS OPTION IN THIS PLUGIN
986         assert(param->getNOptions() == eLuminanceMathRec709);
987         param->appendOption(kParamLuminanceMathOptionRec709);
988         assert(param->getNOptions() == eLuminanceMathRec2020);
989         param->appendOption(kParamLuminanceMathOptionRec2020);
990         assert(param->getNOptions() == eLuminanceMathACESAP0);
991         param->appendOption(kParamLuminanceMathOptionACESAP0);
992         assert(param->getNOptions() == eLuminanceMathACESAP1);
993         param->appendOption(kParamLuminanceMathOptionACESAP1);
994         assert(param->getNOptions() == eLuminanceMathCcir601);
995         param->appendOption(kParamLuminanceMathOptionCcir601);
996         assert(param->getNOptions() == eLuminanceMathAverage);
997         param->appendOption(kParamLuminanceMathOptionAverage);
998         assert(param->getNOptions() == eLuminanceMathMaximum);
999         param->appendOption(kParamLuminanceMathOptionMaximum);
1000         if (page) {
1001             page->addChild(*param);
1002         }
1003     }
1004     {
1005         BooleanParamDescriptor *param = desc.defineBooleanParam(kParamClampBlack);
1006         param->setLabel(kParamClampBlackLabel);
1007         param->setHint(kParamClampBlackHint);
1008         param->setDefault(false);
1009         param->setAnimates(true);
1010         param->setLayoutHint(eLayoutHintNoNewLine, 0);
1011         if (page) {
1012             page->addChild(*param);
1013         }
1014     }
1015     {
1016         BooleanParamDescriptor *param = desc.defineBooleanParam(kParamClampWhite);
1017         param->setLabel(kParamClampWhiteLabel);
1018         param->setHint(kParamClampWhiteHint);
1019         param->setDefault(false);
1020         param->setAnimates(true);
1021         if (page) {
1022             page->addChild(*param);
1023         }
1024     }
1025 
1026     ofxsPremultDescribeParams(desc, page);
1027     {
1028         BooleanParamDescriptor *param = desc.defineBooleanParam(kParamMixLuminanceEnable);
1029         param->setLabel(kParamMixLuminanceEnableLabel);
1030         param->setHint(kParamMixLuminanceEnableHint);
1031         param->setDefault(true);
1032         param->setAnimates(false);
1033         param->setLayoutHint(eLayoutHintNoNewLine, 1);
1034         if (page) {
1035             page->addChild(*param);
1036         }
1037     }
1038     {
1039         DoubleParamDescriptor *param = desc.defineDoubleParam(kParamMixLuminance);
1040         param->setLabel(kParamMixLuminanceLabel);
1041         param->setHint(kParamMixLuminanceHint);
1042         param->setDefault(0.);
1043         param->setRange(0., 1.);
1044         param->setDisplayRange(0., 1.);
1045         param->setAnimates(true);
1046         if (page) {
1047             page->addChild(*param);
1048         }
1049     }
1050 
1051     ofxsMaskMixDescribeParams(desc, page);
1052 
1053     {
1054         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamPremultChanged);
1055         param->setDefault(false);
1056         param->setIsSecretAndDisabled(true);
1057         param->setAnimates(false);
1058         param->setEvaluateOnChange(false);
1059         if (page) {
1060             page->addChild(*param);
1061         }
1062     }
1063 } // HueCorrectPluginFactory::describeInContext
1064 
1065 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)1066 HueCorrectPluginFactory::createInstance(OfxImageEffectHandle handle,
1067                                         ContextEnum /*context*/)
1068 {
1069     return new HueCorrectPlugin(handle);
1070 }
1071 
1072 static HueCorrectPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
1073 mRegisterPluginFactoryInstance(p)
1074 
1075 
1076 //////////////////////////////////////////////////////////////////////////////////
1077 //////////////////////////////////////////////////////////////////////////////////
1078 //////////////////////////////////////////////////////////////////////////////////
1079 //////////////////////////////////////////////////////////////////////////////////
1080 //
1081 // HueKeyer
1082 //
1083 //////////////////////////////////////////////////////////////////////////////////
1084 
1085 #define kPluginKeyerName "HueKeyerOFX"
1086 #define kPluginKeyerGrouping "Keyer"
1087 #define kPluginKeyerDescription \
1088     "Compute a key depending on hue value.\n" \
1089     "Hue and saturation are computed from the the source RGB values. Depending on the hue value, the various adjustment values are computed, and then applied:\n" \
1090     "amount: output transparency for the given hue (amount=1 means alpha=0).\n" \
1091     "sat_thrsh: if source saturation is below this value, the output transparency is gradually decreased."
1092 
1093 #define kPluginKeyerIdentifier "net.sf.openfx.HueKeyer"
1094 
1095 #define kParamKeyerHue "hue"
1096 #define kParamKeyerHueLabel "Hue Curves"
1097 #define kParamKeyerHueHint "Hue-dependent alpha lookup curves:\n" \
1098     "amount: transparency (1-alpha) amount for the given hue\n" \
1099     "sat_thrsh: if source saturation is below this value, transparency is decreased progressively."
1100 
1101 #define kCurveKeyerAmount 0
1102 #define kCurveKeyerSatThrsh 1
1103 #define kCurveKeyerNb 2
1104 
1105 
1106 class HueKeyerProcessorBase
1107     : public ImageProcessor
1108 {
1109 protected:
1110     const Image *_srcImg;
1111 
1112 public:
HueKeyerProcessorBase(ImageEffect & instance)1113     HueKeyerProcessorBase(ImageEffect &instance)
1114         : ImageProcessor(instance)
1115         , _srcImg(NULL)
1116     {
1117     }
1118 
setSrcImg(const Image * v)1119     void setSrcImg(const Image *v) {_srcImg = v; }
1120 };
1121 
1122 template<class PIX, int maxValue>
1123 static float
sampleToFloat(PIX value)1124 sampleToFloat(PIX value)
1125 {
1126     return (maxValue == 1) ? value : (value / (float)maxValue);
1127 }
1128 
1129 template<class PIX, int maxValue>
1130 static PIX
floatToSample(float value)1131 floatToSample(float value)
1132 {
1133     if (maxValue == 1) {
1134         return PIX(value);
1135     }
1136     if (value <= 0) {
1137         return 0;
1138     } else if (value >= 1.) {
1139         return maxValue;
1140     }
1141 
1142     return PIX(value * maxValue + 0.5f);
1143 }
1144 
1145 template<class PIX, int maxValue>
1146 static PIX
floatToSample(double value)1147 floatToSample(double value)
1148 {
1149     if (maxValue == 1) {
1150         return PIX(value);
1151     }
1152     if (value <= 0) {
1153         return 0;
1154     } else if (value >= 1.) {
1155         return maxValue;
1156     }
1157 
1158     return PIX(value * maxValue + 0.5);
1159 }
1160 
1161 template <class PIX, int nComponents, int maxValue, int nbValues>
1162 class HueKeyerProcessor
1163     : public HueKeyerProcessorBase
1164 {
1165 private:
1166     std::vector<double> _hue[kCurveKeyerNb];
1167     ParametricParam*  _hueParam;
1168     double _time;
1169 
1170 public:
1171     // ctor
HueKeyerProcessor(ImageEffect & instance,const RenderArguments & args,ParametricParam * hueParam)1172     HueKeyerProcessor(ImageEffect &instance,
1173                       const RenderArguments &args,
1174                       ParametricParam  *hueParam)
1175         : HueKeyerProcessorBase(instance)
1176         , _hueParam(hueParam)
1177     {
1178         assert(nComponents == 4);
1179         // build the LUT
1180         assert(_hueParam);
1181         _time = args.time;
1182         for (int c = 0; c < kCurveKeyerNb; ++c) {
1183             _hue[c].resize(nbValues + 1);
1184             for (int position = 0; position <= nbValues; ++position) {
1185                 // position to evaluate the param at
1186                 double parametricPos = 6 * double(position) / nbValues;
1187 
1188                 // evaluate the parametric param
1189                 double value = _hueParam->getValue(c, _time, parametricPos);
1190 
1191                 // all the values (in HueKeyer) must be positive. We don't care if sat_thrsh goes above 1.
1192                 value = std::max(0., value);
1193                 // set that in the lut
1194                 _hue[c][position] = value;
1195             }
1196         }
1197     }
1198 
1199 private:
1200     // and do some processing
multiThreadProcessImages(OfxRectI procWindow)1201     void multiThreadProcessImages(OfxRectI procWindow)
1202     {
1203         assert(nComponents == 4);
1204         assert(_dstImg);
1205         for (int y = procWindow.y1; y < procWindow.y2; y++) {
1206             if ( _effect.abort() ) {
1207                 break;
1208             }
1209 
1210             PIX *dstPix = (PIX *) _dstImg->getPixelAddress(procWindow.x1, y);
1211 
1212             for (int x = procWindow.x1; x < procWindow.x2; x++) {
1213                 const PIX *srcPix = (const PIX *)  (_srcImg ? _srcImg->getPixelAddress(x, y) : 0);
1214                 if (!srcPix) {
1215                     std::fill( dstPix, dstPix + 3, PIX() );
1216                     dstPix[3] = maxValue;
1217                 } else {
1218                     std::copy(srcPix, srcPix + 3, dstPix);
1219                     float r = sampleToFloat<PIX, maxValue>(srcPix[0]);
1220                     float g = sampleToFloat<PIX, maxValue>(srcPix[1]);
1221                     float b = sampleToFloat<PIX, maxValue>(srcPix[2]);
1222                     float h, s, v;
1223                     Color::rgb_to_hsv( r, g, b, &h, &s, &v );
1224                     h = h * 6 + 1;
1225                     if (h > 6) {
1226                         h -= 6;
1227                     }
1228                     double amount = interpolate(kCurveKeyerAmount, h);
1229                     double sat_thrsh = interpolate(kCurveKeyerSatThrsh, h);
1230                     float a = 0.;
1231                     if (s == 0) {
1232                         // saturation is 0, hue is undetermined
1233                         a = 0.;
1234                     } else if (s >= sat_thrsh) {
1235                         a = amount;
1236                     } else {
1237                         a = amount * s / sat_thrsh;
1238                     }
1239                     std::copy(srcPix, srcPix + 3, dstPix);
1240                     dstPix[3] = floatToSample<PIX, maxValue>(1. - a);
1241                 }
1242                 // increment the dst pixel
1243                 dstPix += nComponents;
1244             }
1245         }
1246     }
1247 
interpolate(int c,double value)1248     double interpolate(int c, // the curve number
1249                        double value)
1250     {
1251         if ( (value < 0.) || (6. < value) ) {
1252             // slow version
1253             double ret = _hueParam->getValue(c, _time, value);
1254 
1255             return ret;
1256         } else {
1257             double x = value / 6.;
1258             int i = (int)(x * nbValues);
1259             assert(0 <= i && i <= nbValues);
1260             double alpha = std::max( 0., std::min(x * nbValues - i, 1.) );
1261             double a = _hue[c][i];
1262             double b = (i  < nbValues) ? _hue[c][i + 1] : 0.f;
1263 
1264             return a * (1.f - alpha) + b * alpha;
1265         }
1266     }
1267 };
1268 
1269 ////////////////////////////////////////////////////////////////////////////////
1270 /** @brief The plugin that does our work */
1271 class HueKeyerPlugin
1272     : public ImageEffect
1273 {
1274 public:
HueKeyerPlugin(OfxImageEffectHandle handle)1275     HueKeyerPlugin(OfxImageEffectHandle handle)
1276         : ImageEffect(handle)
1277         , _dstClip(NULL)
1278         , _srcClip(NULL)
1279     {
1280         _dstClip = fetchClip(kOfxImageEffectOutputClipName);
1281         assert( _dstClip && (!_dstClip->isConnected() || _dstClip->getPixelComponents() == ePixelComponentAlpha ||
1282                              _dstClip->getPixelComponents() == ePixelComponentRGB ||
1283                              _dstClip->getPixelComponents() == ePixelComponentRGBA) );
1284         _srcClip = getContext() == eContextGenerator ? NULL : fetchClip(kOfxImageEffectSimpleSourceClipName);
1285         assert( (!_srcClip && getContext() == eContextGenerator) ||
1286                 ( _srcClip && (!_srcClip->isConnected() || _srcClip->getPixelComponents() ==  ePixelComponentAlpha ||
1287                                _srcClip->getPixelComponents() == ePixelComponentRGB ||
1288                                _srcClip->getPixelComponents() == ePixelComponentRGBA) ) );
1289 
1290         _hue = fetchParametricParam(kParamKeyerHue);
1291     }
1292 
1293 private:
1294     virtual void render(const RenderArguments &args) OVERRIDE FINAL;
1295 
1296     template <int nComponents>
1297     void renderForComponents(const RenderArguments &args, BitDepthEnum dstBitDepth);
1298 
1299     void setupAndProcess(HueKeyerProcessorBase &, const RenderArguments &args);
1300 
1301 private:
1302     Clip *_dstClip;
1303     Clip *_srcClip;
1304     ParametricParam  *_hue;
1305 };
1306 
1307 
1308 void
setupAndProcess(HueKeyerProcessorBase & processor,const RenderArguments & args)1309 HueKeyerPlugin::setupAndProcess(HueKeyerProcessorBase &processor,
1310                                 const RenderArguments &args)
1311 {
1312     const double time = args.time;
1313 
1314     assert(_dstClip);
1315     auto_ptr<Image> dst( _dstClip->fetchImage(time) );
1316     if ( !dst.get() ) {
1317         throwSuiteStatusException(kOfxStatFailed);
1318     }
1319     BitDepthEnum dstBitDepth    = dst->getPixelDepth();
1320     PixelComponentEnum dstComponents  = dst->getPixelComponents();
1321     if ( ( dstBitDepth != _dstClip->getPixelDepth() ) ||
1322          ( dstComponents != _dstClip->getPixelComponents() ) ) {
1323         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong depth or components");
1324         throwSuiteStatusException(kOfxStatFailed);
1325     }
1326     if ( (dst->getRenderScale().x != args.renderScale.x) ||
1327          ( dst->getRenderScale().y != args.renderScale.y) ||
1328          ( ( dst->getField() != eFieldNone) /* for DaVinci Resolve */ && ( dst->getField() != args.fieldToRender) ) ) {
1329         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
1330         throwSuiteStatusException(kOfxStatFailed);
1331     }
1332     auto_ptr<const Image> src( ( _srcClip && _srcClip->isConnected() ) ?
1333                                     _srcClip->fetchImage(time) : 0 );
1334     if ( src.get() ) {
1335         if ( (src->getRenderScale().x != args.renderScale.x) ||
1336              ( src->getRenderScale().y != args.renderScale.y) ||
1337              ( ( src->getField() != eFieldNone) /* for DaVinci Resolve */ && ( src->getField() != args.fieldToRender) ) ) {
1338             setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
1339             throwSuiteStatusException(kOfxStatFailed);
1340         }
1341         BitDepthEnum srcBitDepth      = src->getPixelDepth();
1342         PixelComponentEnum srcComponents = src->getPixelComponents();
1343         if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
1344             throwSuiteStatusException(kOfxStatErrImageFormat);
1345         }
1346     }
1347 
1348     if ( src.get() && dst.get() ) {
1349         BitDepthEnum srcBitDepth      = src->getPixelDepth();
1350         PixelComponentEnum srcComponents = src->getPixelComponents();
1351         BitDepthEnum dstBitDepth       = dst->getPixelDepth();
1352         PixelComponentEnum dstComponents  = dst->getPixelComponents();
1353 
1354         // see if they have the same depths and bytes and all
1355         if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
1356             throwSuiteStatusException(kOfxStatErrImageFormat);
1357         }
1358     }
1359 
1360     processor.setDstImg( dst.get() );
1361     processor.setSrcImg( src.get() );
1362     processor.setRenderWindow(args.renderWindow);
1363     processor.process();
1364 } // HueKeyerPlugin::setupAndProcess
1365 
1366 // the internal render function
1367 template <int nComponents>
1368 void
renderForComponents(const RenderArguments & args,BitDepthEnum dstBitDepth)1369 HueKeyerPlugin::renderForComponents(const RenderArguments &args,
1370                                     BitDepthEnum dstBitDepth)
1371 {
1372     switch (dstBitDepth) {
1373     case eBitDepthUByte: {
1374         HueKeyerProcessor<unsigned char, nComponents, 255, 255> fred(*this, args, _hue);
1375         setupAndProcess(fred, args);
1376         break;
1377     }
1378     case eBitDepthUShort: {
1379         HueKeyerProcessor<unsigned short, nComponents, 65535, 65535> fred(*this, args, _hue);
1380         setupAndProcess(fred, args);
1381         break;
1382     }
1383     case eBitDepthFloat: {
1384         HueKeyerProcessor<float, nComponents, 1, 1023> fred(*this, args, _hue);
1385         setupAndProcess(fred, args);
1386         break;
1387     }
1388     default:
1389         throwSuiteStatusException(kOfxStatErrUnsupported);
1390     }
1391 }
1392 
1393 void
render(const RenderArguments & args)1394 HueKeyerPlugin::render(const RenderArguments &args)
1395 {
1396     BitDepthEnum dstBitDepth    = _dstClip->getPixelDepth();
1397     PixelComponentEnum dstComponents  = _dstClip->getPixelComponents();
1398 
1399     assert( kSupportsMultipleClipPARs   || !_srcClip || _srcClip->getPixelAspectRatio() == _dstClip->getPixelAspectRatio() );
1400     assert( kSupportsMultipleClipDepths || !_srcClip || _srcClip->getPixelDepth()       == _dstClip->getPixelDepth() );
1401     if (dstComponents == ePixelComponentRGBA) {
1402         renderForComponents<4>(args, dstBitDepth);
1403     } else {
1404         throwSuiteStatusException(kOfxStatErrFormat);
1405     }
1406 }
1407 
1408 mDeclarePluginFactory(HueKeyerPluginFactory, {ofxsThreadSuiteCheck();}, {});
1409 void
describe(ImageEffectDescriptor & desc)1410 HueKeyerPluginFactory::describe(ImageEffectDescriptor &desc)
1411 {
1412     desc.setLabel(kPluginKeyerName);
1413     desc.setPluginGrouping(kPluginKeyerGrouping);
1414     desc.setPluginDescription(kPluginKeyerDescription);
1415 
1416     desc.addSupportedContext(eContextFilter);
1417     //desc.addSupportedContext(eContextPaint);
1418     desc.addSupportedContext(eContextGeneral);
1419     desc.addSupportedBitDepth(eBitDepthUByte);
1420     desc.addSupportedBitDepth(eBitDepthUShort);
1421     desc.addSupportedBitDepth(eBitDepthFloat);
1422 
1423     desc.setSingleInstance(false);
1424     desc.setHostFrameThreading(false);
1425     desc.setSupportsMultiResolution(kSupportsMultiResolution);
1426     desc.setSupportsTiles(kSupportsTiles);
1427     desc.setTemporalClipAccess(false);
1428     desc.setRenderTwiceAlways(false);
1429     desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
1430     desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);
1431     desc.setRenderThreadSafety(kRenderThreadSafety);
1432     // returning an error here crashes Nuke
1433     //if (!getImageEffectHostDescription()->supportsParametricParameter) {
1434     //  throwHostMissingSuiteException(kOfxParametricParameterSuite);
1435     //}
1436 #ifdef OFX_EXTENSIONS_NATRON
1437     desc.setChannelSelector(ePixelComponentNone);
1438 #endif
1439 }
1440 
1441 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum)1442 HueKeyerPluginFactory::describeInContext(ImageEffectDescriptor &desc,
1443                                          ContextEnum /*context*/)
1444 {
1445     const ImageEffectHostDescription &gHostDescription = *getImageEffectHostDescription();
1446     const bool supportsParametricParameter = ( gHostDescription.supportsParametricParameter &&
1447                                                !(gHostDescription.hostName == "uk.co.thefoundry.nuke" &&
1448                                                  8 <= gHostDescription.versionMajor && gHostDescription.versionMajor <= 10) ); // Nuke 8-10 are known to *not* support Parametric
1449 
1450     if (!supportsParametricParameter) {
1451         throwHostMissingSuiteException(kOfxParametricParameterSuite);
1452     }
1453 
1454     ClipDescriptor *srcClip = desc.defineClip(kOfxImageEffectSimpleSourceClipName);
1455     assert(srcClip);
1456     srcClip->addSupportedComponent(ePixelComponentRGBA);
1457     //srcClip->addSupportedComponent(ePixelComponentRGB);
1458 #ifdef OFX_EXTENSIONS_NATRON
1459     //srcClip->addSupportedComponent(ePixelComponentXY);
1460 #endif
1461     //srcClip->addSupportedComponent(ePixelComponentAlpha);
1462     srcClip->setTemporalClipAccess(false);
1463     srcClip->setSupportsTiles(kSupportsTiles);
1464     srcClip->setIsMask(false);
1465 
1466     ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
1467     assert(dstClip);
1468     dstClip->addSupportedComponent(ePixelComponentRGBA);
1469     //dstClip->addSupportedComponent(ePixelComponentRGB);
1470 #ifdef OFX_EXTENSIONS_NATRON
1471     //dstClip->addSupportedComponent(ePixelComponentXY);
1472 #endif
1473     //dstClip->addSupportedComponent(ePixelComponentAlpha);
1474     dstClip->setSupportsTiles(kSupportsTiles);
1475 
1476 
1477     // make some pages and to things in
1478     PageParamDescriptor *page = desc.definePageParam("Controls");
1479 
1480     // define it
1481     {
1482         ParametricParamDescriptor* param = desc.defineParametricParam(kParamKeyerHue);
1483         assert(param);
1484         param->setPeriodic(true);
1485         param->setLabel(kParamKeyerHueLabel);
1486         param->setHint(kParamKeyerHueHint);
1487         {
1488             HueCorrectInteractDescriptor* interact = new HueCorrectInteractDescriptor;
1489             param->setInteractDescriptor(interact);
1490         }
1491 
1492         // define it as three dimensional
1493         param->setDimension(kCurveKeyerNb);
1494 
1495         // label our dimensions are r/g/b
1496         param->setDimensionLabel("amount", kCurveKeyerAmount);
1497         param->setDimensionLabel("sat_thrsh", kCurveKeyerSatThrsh);
1498 
1499         // set the UI colour for each dimension
1500         //const OfxRGBColourD master  = {0.9, 0.9, 0.9};
1501         // the following are magic colors, they all have the same Rec709 luminance
1502         //const OfxRGBColourD red   = {0.711519527404004, 0.164533420851110, 0.164533420851110};      //set red color to red curve
1503         //const OfxRGBColourD green = {0., 0.546986106552894, 0.};        //set green color to green curve
1504         //const OfxRGBColourD blue  = {0.288480472595996, 0.288480472595996, 0.835466579148890};      //set blue color to blue curve
1505         const OfxRGBColourD alpha  = {0.398979, 0.398979, 0.398979};
1506         const OfxRGBColourD yellow  = {0.711519527404004, 0.711519527404004, 0.164533420851110};
1507         param->setUIColour( kCurveKeyerAmount, alpha );
1508         param->setUIColour( kCurveKeyerSatThrsh, yellow );
1509 
1510         // set the min/max parametric range to 0..6
1511         param->setRange(0.0, 6.0);
1512         // set the default Y range to 0..1 for all dimensions
1513         param->setDimensionDisplayRange(0., 1., kCurveKeyerAmount);
1514         param->setDimensionDisplayRange(0., 1., kCurveKeyerSatThrsh);
1515 
1516 
1517         int plast = param->supportsPeriodic() ? 5 : 6;
1518         // set a default curve
1519         for (int c = 0; c < kCurveKeyerNb; ++c) {
1520             // minimum/maximum: are these supported by OpenFX?
1521             param->setDimensionRange(0., 1., c);
1522             param->setDimensionDisplayRange(0., 1., c);
1523             for (int p = 0; p <= plast; ++p) {
1524                 // add a control point at p
1525                 param->addControlPoint(c, // curve to set
1526                                        0.0,   // time, ignored in this case, as we are not adding a key
1527                                        p,   // parametric position, zero
1528                                        (c == kCurveKeyerSatThrsh) ? 0.1 : (double)(p == 3 || p == 4),   // value to be
1529                                        false);   // don't add a key
1530             }
1531         }
1532 
1533         if (page) {
1534             page->addChild(*param);
1535         }
1536     }
1537 } // HueKeyerPluginFactory::describeInContext
1538 
1539 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)1540 HueKeyerPluginFactory::createInstance(OfxImageEffectHandle handle,
1541                                       ContextEnum /*context*/)
1542 {
1543     return new HueKeyerPlugin(handle);
1544 }
1545 
1546 static HueKeyerPluginFactory p1(kPluginKeyerIdentifier, kPluginVersionMajor, kPluginVersionMinor);
1547 mRegisterPluginFactoryInstance(p1)
1548 
1549 OFXS_NAMESPACE_ANONYMOUS_EXIT
1550