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 Ramp plugin.
21  */
22 
23 #include <cmath>
24 #include <algorithm>
25 
26 #include "ofxsProcessing.H"
27 #include "ofxsCoords.h"
28 #include "ofxsMaskMix.h"
29 #include "ofxsMacros.h"
30 #include "ofxsRamp.h"
31 #ifdef OFX_EXTENSIONS_NATRON
32 #include "ofxNatron.h"
33 #endif
34 #include "ofxsThreadSuite.h"
35 
36 #ifdef __APPLE__
37 #include <OpenGL/gl.h>
38 #else
39 #ifdef _WIN32
40 #define WIN32_LEAN_AND_MEAN
41 #ifndef NOMINMAX
42 #define NOMINMAX
43 #endif
44 #include <windows.h>
45 #endif
46 
47 #include <GL/gl.h>
48 #endif
49 
50 using namespace OFX;
51 
52 OFXS_NAMESPACE_ANONYMOUS_ENTER
53 
54 #define kPluginName "RampOFX"
55 #define kPluginGrouping "Draw"
56 #define kPluginDescription \
57     "Draw a ramp between 2 edges.\n" \
58     "The ramp is composited with the source image using the 'over' operator.\n" \
59     "See also: http://opticalenquiry.com/nuke/index.php?title=Ramp"
60 
61 #define kPluginIdentifier "net.sf.openfx.Ramp"
62 // History:
63 // version 1.0: initial version
64 // version 2.0: use kNatronOfxParamProcess* parameters
65 #define kPluginVersionMajor 2 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
66 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
67 
68 #define kSupportsTiles 1
69 #define kSupportsMultiResolution 1
70 #define kSupportsRenderScale 1
71 #define kSupportsMultipleClipPARs false
72 #define kSupportsMultipleClipDepths false
73 #define kRenderThreadSafety eRenderFullySafe
74 
75 #ifdef OFX_EXTENSIONS_NATRON
76 #define kParamProcessR kNatronOfxParamProcessR
77 #define kParamProcessRLabel kNatronOfxParamProcessRLabel
78 #define kParamProcessRHint kNatronOfxParamProcessRHint
79 #define kParamProcessG kNatronOfxParamProcessG
80 #define kParamProcessGLabel kNatronOfxParamProcessGLabel
81 #define kParamProcessGHint kNatronOfxParamProcessGHint
82 #define kParamProcessB kNatronOfxParamProcessB
83 #define kParamProcessBLabel kNatronOfxParamProcessBLabel
84 #define kParamProcessBHint kNatronOfxParamProcessBHint
85 #define kParamProcessA kNatronOfxParamProcessA
86 #define kParamProcessALabel kNatronOfxParamProcessALabel
87 #define kParamProcessAHint kNatronOfxParamProcessAHint
88 #else
89 #define kParamProcessR      "processR"
90 #define kParamProcessRLabel "R"
91 #define kParamProcessRHint  "Process red component."
92 #define kParamProcessG      "processG"
93 #define kParamProcessGLabel "G"
94 #define kParamProcessGHint  "Process green component."
95 #define kParamProcessB      "processB"
96 #define kParamProcessBLabel "B"
97 #define kParamProcessBHint  "Process blue component."
98 #define kParamProcessA      "processA"
99 #define kParamProcessALabel "A"
100 #define kParamProcessAHint  "Process alpha component."
101 #endif
102 
103 #ifdef OFX_EXTENSIONS_NATRON
104 #define OFX_COMPONENTS_OK(c) ((c)== ePixelComponentAlpha || (c) == ePixelComponentXY || (c) == ePixelComponentRGB || (c) == ePixelComponentRGBA)
105 #else
106 #define OFX_COMPONENTS_OK(c) ((c)== ePixelComponentAlpha || (c) == ePixelComponentRGB || (c) == ePixelComponentRGBA)
107 #endif
108 
109 
110 struct RGBAValues
111 {
112     double r, g, b, a;
RGBAValuesRGBAValues113     RGBAValues(double v) : r(v), g(v), b(v), a(v) {}
114 
RGBAValuesRGBAValues115     RGBAValues() : r(0), g(0), b(0), a(0) {}
116 };
117 
118 
119 class RampProcessorBase
120     : public ImageProcessor
121 {
122 protected:
123     const Image *_srcImg;
124     const Image *_maskImg;
125     bool _doMasking;
126     double _mix;
127     bool _maskInvert;
128     bool _processR;
129     bool _processG;
130     bool _processB;
131     bool _processA;
132     RampTypeEnum _type;
133     RGBAValues _color0, _color1;
134     OfxPointD _point0, _point1;
135 
136 public:
RampProcessorBase(ImageEffect & instance)137     RampProcessorBase(ImageEffect &instance)
138         : ImageProcessor(instance)
139         , _srcImg(NULL)
140         , _maskImg(NULL)
141         , _doMasking(false)
142         , _mix(1.)
143         , _maskInvert(false)
144         , _processR(false)
145         , _processG(false)
146         , _processB(false)
147         , _processA(false)
148         , _type(eRampTypeLinear)
149     {
150         _point0.x = _point0.y = _point1.x = _point1.y = 0.;
151         _color0.r = _color0.g = _color0.b = _color0.a = 0.;
152         _color1.r = _color1.g = _color1.b = _color1.a = 0.;
153     }
154 
155     /** @brief set the src image */
setSrcImg(const Image * v)156     void setSrcImg(const Image *v)
157     {
158         _srcImg = v;
159     }
160 
setMaskImg(const Image * v,bool maskInvert)161     void setMaskImg(const Image *v,
162                     bool maskInvert)
163     {
164         _maskImg = v;
165         _maskInvert = maskInvert;
166     }
167 
doMasking(bool v)168     void doMasking(bool v)
169     {
170         _doMasking = v;
171     }
172 
setValues(RampTypeEnum type,const RGBAValues & color0,const RGBAValues & color1,const OfxPointD & point0,const OfxPointD & point1,double mix,bool processR,bool processG,bool processB,bool processA)173     void setValues(RampTypeEnum type,
174                    const RGBAValues& color0,
175                    const RGBAValues& color1,
176                    const OfxPointD& point0,
177                    const OfxPointD& point1,
178                    double mix,
179                    bool processR,
180                    bool processG,
181                    bool processB,
182                    bool processA)
183     {
184         _type = type;
185         _color0 = color0;
186         _color1 = color1;
187         _point0 = point0;
188         _point1 = point1;
189         _mix = mix;
190         _processR = processR;
191         _processG = processG;
192         _processB = processB;
193         _processA = processA;
194     }
195 };
196 
197 
198 template <class PIX, int nComponents, int maxValue>
199 class RampProcessor
200     : public RampProcessorBase
201 {
202 public:
RampProcessor(ImageEffect & instance)203     RampProcessor(ImageEffect &instance)
204         : RampProcessorBase(instance)
205     {
206     }
207 
208 private:
multiThreadProcessImages(OfxRectI procWindow)209     void multiThreadProcessImages(OfxRectI procWindow)
210     {
211 #     ifndef __COVERITY__ // too many coverity[dead_error_line] errors
212         const bool r = _processR && (nComponents != 1);
213         const bool g = _processG && (nComponents >= 2);
214         const bool b = _processB && (nComponents >= 3);
215         const bool a = _processA && (nComponents == 1 || nComponents == 4);
216         if (r) {
217             if (g) {
218                 if (b) {
219                     if (a) {
220                         return process<true, true, true, true >(procWindow); // RGBA
221                     } else {
222                         return process<true, true, true, false>(procWindow); // RGBa
223                     }
224                 } else {
225                     if (a) {
226                         return process<true, true, false, true >(procWindow); // RGbA
227                     } else {
228                         return process<true, true, false, false>(procWindow); // RGba
229                     }
230                 }
231             } else {
232                 if (b) {
233                     if (a) {
234                         return process<true, false, true, true >(procWindow); // RgBA
235                     } else {
236                         return process<true, false, true, false>(procWindow); // RgBa
237                     }
238                 } else {
239                     if (a) {
240                         return process<true, false, false, true >(procWindow); // RgbA
241                     } else {
242                         return process<true, false, false, false>(procWindow); // Rgba
243                     }
244                 }
245             }
246         } else {
247             if (g) {
248                 if (b) {
249                     if (a) {
250                         return process<false, true, true, true >(procWindow); // rGBA
251                     } else {
252                         return process<false, true, true, false>(procWindow); // rGBa
253                     }
254                 } else {
255                     if (a) {
256                         return process<false, true, false, true >(procWindow); // rGbA
257                     } else {
258                         return process<false, true, false, false>(procWindow); // rGba
259                     }
260                 }
261             } else {
262                 if (b) {
263                     if (a) {
264                         return process<false, false, true, true >(procWindow); // rgBA
265                     } else {
266                         return process<false, false, true, false>(procWindow); // rgBa
267                     }
268                 } else {
269                     if (a) {
270                         return process<false, false, false, true >(procWindow); // rgbA
271                     } else {
272                         return process<false, false, false, false>(procWindow); // rgba
273                     }
274                 }
275             }
276         }
277 #     endif // ifndef __COVERITY__
278     } // multiThreadProcessImages
279 
280     template<bool processR, bool processG, bool processB, bool processA>
process(const OfxRectI & procWindow)281     void process(const OfxRectI& procWindow)
282     {
283         assert( (!processR && !processG && !processB) || (nComponents == 3 || nComponents == 4) );
284         assert( !processA || (nComponents == 1 || nComponents == 4) );
285         switch (_type) {
286         case eRampTypeLinear:
287             processForType<processR, processG, processB, processA, eRampTypeLinear>(procWindow);
288             break;
289         case eRampTypePLinear:
290             processForType<processR, processG, processB, processA, eRampTypePLinear>(procWindow);
291             break;
292         case eRampTypeEaseIn:
293             processForType<processR, processG, processB, processA, eRampTypeEaseIn>(procWindow);
294             break;
295         case eRampTypeEaseOut:
296             processForType<processR, processG, processB, processA, eRampTypeEaseOut>(procWindow);
297             break;
298         case eRampTypeSmooth:
299             processForType<processR, processG, processB, processA, eRampTypeSmooth>(procWindow);
300             break;
301         case eRampTypeNone:
302             processForType<processR, processG, processB, processA, eRampTypeNone>(procWindow);
303             break;
304         }
305     }
306 
307     template<bool processR, bool processG, bool processB, bool processA, RampTypeEnum type>
processForType(const OfxRectI & procWindow)308     void processForType(const OfxRectI& procWindow)
309     {
310         float tmpPix[4];
311         const double norm2 = (_point1.x - _point0.x) * (_point1.x - _point0.x) + (_point1.y - _point0.y) * (_point1.y - _point0.y);
312         const double nx = norm2 == 0. ? 0. : (_point1.x - _point0.x) / norm2;
313         const double ny = norm2 == 0. ? 0. : (_point1.y - _point0.y) / norm2;
314 
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, dstPix += nComponents) {
323                 const PIX *srcPix = (const PIX *)  (_srcImg ? _srcImg->getPixelAddress(x, y) : 0);
324                 OfxPointI p_pixel;
325                 OfxPointD p;
326                 p_pixel.x = x;
327                 p_pixel.y = y;
328                 Coords::toCanonical(p_pixel, _dstImg->getRenderScale(), _dstImg->getPixelAspectRatio(), &p);
329                 double t = ofxsRampFunc<type>(_point0, nx, ny, p);
330 
331                 tmpPix[0] = (float)_color0.r * (1 - (float)t) + (float)_color1.r * (float)t;
332                 tmpPix[1] = (float)_color0.g * (1 - (float)t) + (float)_color1.g * (float)t;
333                 tmpPix[2] = (float)_color0.b * (1 - (float)t) + (float)_color1.b * (float)t;
334                 tmpPix[3] = (float)_color0.a * (1 - (float)t) + (float)_color1.a * (float)t;
335 
336                 float a = tmpPix[3];
337 
338                 // ofxsMaskMixPix takes non-normalized values
339                 tmpPix[0] *= maxValue;
340                 tmpPix[1] *= maxValue;
341                 tmpPix[2] *= maxValue;
342                 tmpPix[3] *= maxValue;
343                 float srcPixRGBA[4] = {0, 0, 0, 0};
344                 if (srcPix) {
345                     if (nComponents >= 3) {
346                         srcPixRGBA[0] = srcPix[0];
347                         srcPixRGBA[1] = srcPix[1];
348                         srcPixRGBA[2] = srcPix[2];
349                     }
350                     if ( (nComponents == 1) || (nComponents == 4) ) {
351                         srcPixRGBA[3] = srcPix[nComponents - 1];
352                     }
353                 }
354                 if (processR) {
355                     tmpPix[0] = tmpPix[0] + srcPixRGBA[0] * (1.f - a);
356                 } else {
357                     tmpPix[0] = srcPixRGBA[0];
358                 }
359                 if (processG) {
360                     tmpPix[1] = tmpPix[1] + srcPixRGBA[1] * (1.f - a);
361                 } else {
362                     tmpPix[1] = srcPixRGBA[1];
363                 }
364                 if (processB) {
365                     tmpPix[2] = tmpPix[2] + srcPixRGBA[2] * (1.f - a);
366                 } else {
367                     tmpPix[2] = srcPixRGBA[2];
368                 }
369                 if (processA) {
370                     tmpPix[3] = tmpPix[3] + srcPixRGBA[3] * (1.f - a);
371                 } else {
372                     tmpPix[3] = srcPixRGBA[3];
373                 }
374                 ofxsMaskMixPix<PIX, nComponents, maxValue, true>(tmpPix, x, y, srcPix, _doMasking, _maskImg, (float)_mix, _maskInvert, dstPix);
375             }
376         }
377     } // processForType
378 };
379 
380 
381 ////////////////////////////////////////////////////////////////////////////////
382 /** @brief The plugin that does our work */
383 class RampPlugin
384     : public ImageEffect
385 {
386 public:
387     /** @brief ctor */
RampPlugin(OfxImageEffectHandle handle)388     RampPlugin(OfxImageEffectHandle handle)
389         : ImageEffect(handle)
390         , _dstClip(NULL)
391         , _srcClip(NULL)
392         , _processR(NULL)
393         , _processG(NULL)
394         , _processB(NULL)
395         , _processA(NULL)
396         , _point0(NULL)
397         , _color0(NULL)
398         , _point1(NULL)
399         , _color1(NULL)
400         , _type(NULL)
401         , _interactive(NULL)
402         , _mix(NULL)
403         , _maskApply(NULL)
404         , _maskInvert(NULL)
405     {
406         _dstClip = fetchClip(kOfxImageEffectOutputClipName);
407         assert( _dstClip && (!_dstClip->isConnected() || _dstClip->getPixelComponents() == ePixelComponentAlpha ||
408                              _dstClip->getPixelComponents() == ePixelComponentRGB ||
409                              _dstClip->getPixelComponents() == ePixelComponentRGBA) );
410         _srcClip = getContext() == eContextGenerator ? NULL : fetchClip(kOfxImageEffectSimpleSourceClipName);
411         assert( (!_srcClip && getContext() == eContextGenerator) ||
412                 ( _srcClip && (!_srcClip->isConnected() || _srcClip->getPixelComponents() ==  ePixelComponentAlpha ||
413                                _srcClip->getPixelComponents() == ePixelComponentRGB ||
414                                _srcClip->getPixelComponents() == ePixelComponentRGBA) ) );
415         _maskClip = fetchClip(getContext() == eContextPaint ? "Brush" : "Mask");
416         assert(!_maskClip || !_maskClip->isConnected() || _maskClip->getPixelComponents() == ePixelComponentAlpha);
417 
418         _processR = fetchBooleanParam(kParamProcessR);
419         _processG = fetchBooleanParam(kParamProcessG);
420         _processB = fetchBooleanParam(kParamProcessB);
421         _processA = fetchBooleanParam(kParamProcessA);
422         assert(_processR && _processG && _processB && _processA);
423         _point0 = fetchDouble2DParam(kParamRampPoint0Old);
424         _point1 = fetchDouble2DParam(kParamRampPoint1Old);
425         _color0 = fetchRGBAParam(kParamRampColor0Old);
426         _color1 = fetchRGBAParam(kParamRampColor1Old);
427         _type = fetchChoiceParam(kParamRampTypeOld);
428         _interactive = fetchBooleanParam(kParamRampInteractiveOld);
429         assert(_point0 && _point1 && _color0 && _color1 && _type && _interactive);
430 
431         _mix = fetchDoubleParam(kParamMix);
432         _maskApply = ( ofxsMaskIsAlwaysConnected( OFX::getImageEffectHostDescription() ) && paramExists(kParamMaskApply) ) ? fetchBooleanParam(kParamMaskApply) : 0;
433         _maskInvert = fetchBooleanParam(kParamMaskInvert);
434         assert(_mix && _maskInvert);
435 
436         // finally
437         syncPrivateData();
438     }
439 
440 private:
441     /* override is identity */
442     virtual bool isIdentity(const IsIdentityArguments &args, Clip * &identityClip, double &identityTime, int& view, std::string& plane) OVERRIDE FINAL;
443 
444     /* Override the clip preferences */
445     void getClipPreferences(ClipPreferencesSetter &clipPreferences) OVERRIDE FINAL;
446 
447     /* Override the render */
448     virtual void render(const RenderArguments &args) OVERRIDE FINAL;
449     virtual void changedParam(const InstanceChangedArgs &args, const std::string &paramName) OVERRIDE FINAL;
450 
451     template <int nComponents>
452     void renderInternal(const RenderArguments &args, BitDepthEnum dstBitDepth);
453 
454     /* set up and run a processor */
455     void setupAndProcess(RampProcessorBase &, const RenderArguments &args);
456 
457     /** @brief The sync private data action, called when the effect needs to sync any private data to persistent parameters */
syncPrivateData(void)458     virtual void syncPrivateData(void) OVERRIDE FINAL
459     {
460         RampTypeEnum type = (RampTypeEnum)_type->getValue(); // does not animate
461         bool noramp = (type == eRampTypeNone);
462         _color0->setIsSecretAndDisabled(noramp);
463         _point0->setIsSecretAndDisabled(noramp);
464         _point1->setIsSecretAndDisabled(noramp);
465         _interactive->setIsSecretAndDisabled(noramp);
466     }
467 
468 private:
469 
470     // do not need to delete these, the ImageEffect is managing them for us
471     Clip *_dstClip;
472     Clip *_srcClip;
473     Clip *_maskClip;
474     BooleanParam* _processR;
475     BooleanParam* _processG;
476     BooleanParam* _processB;
477     BooleanParam* _processA;
478     Double2DParam* _point0;
479     RGBAParam* _color0;
480     Double2DParam* _point1;
481     RGBAParam* _color1;
482     ChoiceParam* _type;
483     BooleanParam* _interactive;
484     DoubleParam* _mix;
485     BooleanParam* _maskApply;
486     BooleanParam* _maskInvert;
487 };
488 
489 ////////////////////////////////////////////////////////////////////////////////
490 /** @brief render for the filter */
491 
492 ////////////////////////////////////////////////////////////////////////////////
493 // basic plugin render function, just a skelington to instantiate templates from
494 
495 /* set up and run a processor */
496 void
setupAndProcess(RampProcessorBase & processor,const RenderArguments & args)497 RampPlugin::setupAndProcess(RampProcessorBase &processor,
498                             const RenderArguments &args)
499 {
500     auto_ptr<Image> dst( _dstClip->fetchImage(args.time) );
501 
502     if ( !dst.get() ) {
503         throwSuiteStatusException(kOfxStatFailed);
504     }
505     const double time = args.time;
506     BitDepthEnum dstBitDepth    = dst->getPixelDepth();
507     PixelComponentEnum dstComponents  = dst->getPixelComponents();
508     if ( ( dstBitDepth != _dstClip->getPixelDepth() ) ||
509          ( dstComponents != _dstClip->getPixelComponents() ) ) {
510         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong depth or components");
511         throwSuiteStatusException(kOfxStatFailed);
512     }
513     if ( (dst->getRenderScale().x != args.renderScale.x) ||
514          ( dst->getRenderScale().y != args.renderScale.y) ||
515          ( ( dst->getField() != eFieldNone) /* for DaVinci Resolve */ && ( dst->getField() != args.fieldToRender) ) ) {
516         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
517         throwSuiteStatusException(kOfxStatFailed);
518     }
519     auto_ptr<const Image> src( ( _srcClip && _srcClip->isConnected() ) ?
520                                     _srcClip->fetchImage(args.time) : 0 );
521     if ( src.get() ) {
522         if ( (src->getRenderScale().x != args.renderScale.x) ||
523              ( src->getRenderScale().y != args.renderScale.y) ||
524              ( ( src->getField() != eFieldNone) /* for DaVinci Resolve */ && ( src->getField() != args.fieldToRender) ) ) {
525             setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
526             throwSuiteStatusException(kOfxStatFailed);
527         }
528         BitDepthEnum srcBitDepth      = src->getPixelDepth();
529         PixelComponentEnum srcComponents = src->getPixelComponents();
530         if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
531             throwSuiteStatusException(kOfxStatErrImageFormat);
532         }
533     }
534 
535     // auto ptr for the mask.
536     bool doMasking = ( ( !_maskApply || _maskApply->getValueAtTime(args.time) ) && _maskClip && _maskClip->isConnected() );
537     auto_ptr<const Image> mask(doMasking ? _maskClip->fetchImage(args.time) : 0);
538     if (doMasking) {
539         if ( mask.get() ) {
540             if ( (mask->getRenderScale().x != args.renderScale.x) ||
541                  ( mask->getRenderScale().y != args.renderScale.y) ||
542                  ( ( mask->getField() != eFieldNone) /* for DaVinci Resolve */ && ( mask->getField() != args.fieldToRender) ) ) {
543                 setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
544                 throwSuiteStatusException(kOfxStatFailed);
545             }
546         }
547         bool maskInvert;
548         _maskInvert->getValueAtTime(args.time, maskInvert);
549         processor.doMasking(true);
550         processor.setMaskImg(mask.get(), maskInvert);
551     }
552 
553     if ( src.get() && dst.get() ) {
554         BitDepthEnum srcBitDepth      = src->getPixelDepth();
555         PixelComponentEnum srcComponents = src->getPixelComponents();
556         BitDepthEnum dstBitDepth       = dst->getPixelDepth();
557         PixelComponentEnum dstComponents  = dst->getPixelComponents();
558 
559         // see if they have the same depths and bytes and all
560         if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
561             throwSuiteStatusException(kOfxStatErrImageFormat);
562         }
563     }
564 
565     // set the images
566     processor.setDstImg( dst.get() );
567     processor.setSrcImg( src.get() );
568 
569     // set the render window
570     processor.setRenderWindow(args.renderWindow);
571 
572     RampTypeEnum type = (RampTypeEnum)_type->getValueAtTime(time);
573     OfxPointD point0, point1;
574     _point0->getValueAtTime(args.time, point0.x, point0.y);
575     _point1->getValueAtTime(args.time, point1.x, point1.y);
576 
577     RGBAValues color0, color1;
578     _color0->getValueAtTime(args.time, color0.r, color0.g, color0.b, color0.a);
579     _color1->getValueAtTime(args.time, color1.r, color1.g, color1.b, color1.a);
580 
581     bool processR, processG, processB, processA;
582     _processR->getValueAtTime(time, processR);
583     _processG->getValueAtTime(time, processG);
584     _processB->getValueAtTime(time, processB);
585     _processA->getValueAtTime(time, processA);
586 
587     double mix;
588     _mix->getValueAtTime(args.time, mix);
589 
590     processor.setValues(type,
591                         color0, color1,
592                         point0, point1,
593                         mix,
594                         processR, processG, processB, processA);
595     // Call the base class process member, this will call the derived templated process code
596     processor.process();
597 } // RampPlugin::setupAndProcess
598 
599 // the internal render function
600 template <int nComponents>
601 void
renderInternal(const RenderArguments & args,BitDepthEnum dstBitDepth)602 RampPlugin::renderInternal(const RenderArguments &args,
603                            BitDepthEnum dstBitDepth)
604 {
605     switch (dstBitDepth) {
606     case eBitDepthUByte: {
607         RampProcessor<unsigned char, nComponents, 255> fred(*this);
608         setupAndProcess(fred, args);
609         break;
610     }
611     case eBitDepthUShort: {
612         RampProcessor<unsigned short, nComponents, 65535> fred(*this);
613         setupAndProcess(fred, args);
614         break;
615     }
616     case eBitDepthFloat: {
617         RampProcessor<float, nComponents, 1> fred(*this);
618         setupAndProcess(fred, args);
619         break;
620     }
621     default:
622         throwSuiteStatusException(kOfxStatErrUnsupported);
623     }
624 }
625 
626 // the overridden render function
627 void
render(const RenderArguments & args)628 RampPlugin::render(const RenderArguments &args)
629 {
630     // instantiate the render code based on the pixel depth of the dst clip
631     BitDepthEnum dstBitDepth    = _dstClip->getPixelDepth();
632     PixelComponentEnum dstComponents  = _dstClip->getPixelComponents();
633 
634     assert( kSupportsMultipleClipPARs   || !_srcClip || _srcClip->getPixelAspectRatio() == _dstClip->getPixelAspectRatio() );
635     assert( kSupportsMultipleClipDepths || !_srcClip || _srcClip->getPixelDepth()       == _dstClip->getPixelDepth() );
636     assert(OFX_COMPONENTS_OK(dstComponents));
637     if (dstComponents == ePixelComponentRGBA) {
638         renderInternal<4>(args, dstBitDepth);
639     } else if (dstComponents == ePixelComponentRGB) {
640         renderInternal<3>(args, dstBitDepth);
641 #ifdef OFX_EXTENSIONS_NATRON
642    } else if (dstComponents == ePixelComponentXY) {
643         renderInternal<2>(args, dstBitDepth);
644 #endif
645     } else {
646         assert(dstComponents == ePixelComponentAlpha);
647         renderInternal<1>(args, dstBitDepth);
648     }
649 }
650 
651 bool
isIdentity(const IsIdentityArguments & args,Clip * & identityClip,double &,int &,std::string &)652 RampPlugin::isIdentity(const IsIdentityArguments &args,
653                        Clip * &identityClip,
654                        double & /*identityTime*/
655                        , int& /*view*/, std::string& /*plane*/)
656 {
657     double mix;
658 
659     _mix->getValueAtTime(args.time, mix);
660 
661     if (mix == 0. /*|| (!processR && !processG && !processB && !processA)*/) {
662         identityClip = _srcClip;
663 
664         return true;
665     }
666 
667     {
668         bool processR;
669         bool processG;
670         bool processB;
671         bool processA;
672         _processR->getValueAtTime(args.time, processR);
673         _processG->getValueAtTime(args.time, processG);
674         _processB->getValueAtTime(args.time, processB);
675         _processA->getValueAtTime(args.time, processA);
676         if (!processR && !processG && !processB && !processA) {
677             identityClip = _srcClip;
678 
679             return true;
680         }
681     }
682 
683     RGBAValues color0, color1;
684     _color0->getValueAtTime(args.time, color0.r, color0.g, color0.b, color0.a);
685     _color1->getValueAtTime(args.time, color1.r, color1.g, color1.b, color1.a);
686     if ( (color0.a == 0.) && (color1.a == 0.) ) {
687         identityClip = _srcClip;
688 
689         return true;
690     }
691 
692     bool doMasking = ( ( !_maskApply || _maskApply->getValueAtTime(args.time) ) && _maskClip && _maskClip->isConnected() );
693     if (doMasking) {
694         bool maskInvert;
695         _maskInvert->getValueAtTime(args.time, maskInvert);
696         if (!maskInvert) {
697             OfxRectI maskRoD;
698             if (getImageEffectHostDescription()->supportsMultiResolution) {
699                 // In Sony Catalyst Edit, clipGetRegionOfDefinition returns the RoD in pixels instead of canonical coordinates.
700                 // In hosts that do not support multiResolution (e.g. Sony Catalyst Edit), all inputs have the same RoD anyway.
701                 Coords::toPixelEnclosing(_maskClip->getRegionOfDefinition(args.time), args.renderScale, _maskClip->getPixelAspectRatio(), &maskRoD);
702                 // effect is identity if the renderWindow doesn't intersect the mask RoD
703                 if ( !Coords::rectIntersection<OfxRectI>(args.renderWindow, maskRoD, 0) ) {
704                     identityClip = _srcClip;
705 
706                     return true;
707                 }
708             }
709         }
710     }
711 
712     return false;
713 } // RampPlugin::isIdentity
714 
715 /* Override the clip preferences */
716 void
getClipPreferences(ClipPreferencesSetter & clipPreferences)717 RampPlugin::getClipPreferences(ClipPreferencesSetter &clipPreferences)
718 {
719     if (_srcClip) {
720         // set the premultiplication of _dstClip if alpha is affected and source is Opaque
721         bool processA = _processA->getValue();
722         // Unfortunately, we cannot check the output components as was done in
723         // https://github.com/devernay/openfx-misc/commit/844a442b5baeef4b1e1a0fd4d5e957707f4465ca
724         // since it would call getClipPrefs recursively.
725         // We just set the output components.
726         if ( processA && _srcClip && _srcClip->isConnected() && _srcClip->getPreMultiplication() == eImageOpaque) {
727             clipPreferences.setClipComponents(*_dstClip, ePixelComponentRGBA);
728             clipPreferences.setOutputPremultiplication(eImageUnPreMultiplied);
729         }
730     }
731 
732     // if no input is connected, output is continuous
733     if ( !_srcClip || !_srcClip->isConnected() ) {
734         clipPreferences.setOutputHasContinuousSamples(true);
735     }
736 }
737 
738 void
changedParam(const InstanceChangedArgs & args,const std::string & paramName)739 RampPlugin::changedParam(const InstanceChangedArgs &args,
740                          const std::string &paramName)
741 {
742     if ( (paramName == kParamRampType) && (args.reason == eChangeUserEdit) ) {
743         syncPrivateData();
744     }
745 }
746 
747 //static void intersectToRoD(const OfxRectD& rod,const OfxPointD& p0)
748 
749 
750 mDeclarePluginFactory(RampPluginFactory, {ofxsThreadSuiteCheck();}, {});
751 void
describe(ImageEffectDescriptor & desc)752 RampPluginFactory::describe(ImageEffectDescriptor &desc)
753 {
754     // basic labels
755     desc.setLabel(kPluginName);
756     desc.setPluginGrouping(kPluginGrouping);
757     desc.setPluginDescription(kPluginDescription);
758 
759     desc.addSupportedContext(eContextGeneral);
760     desc.addSupportedContext(eContextGenerator);
761 
762     desc.addSupportedBitDepth(eBitDepthUByte);
763     desc.addSupportedBitDepth(eBitDepthUShort);
764     desc.addSupportedBitDepth(eBitDepthFloat);
765 
766 
767     desc.setSingleInstance(false);
768     desc.setHostFrameThreading(false);
769     desc.setTemporalClipAccess(false);
770     desc.setRenderTwiceAlways(false);
771     desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
772     desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);
773     desc.setRenderThreadSafety(kRenderThreadSafety);
774 
775     desc.setSupportsTiles(kSupportsTiles);
776 
777     // in order to support multiresolution, render() must take into account the pixelaspectratio and the renderscale
778     // and scale the transform appropriately.
779     // All other functions are usually in canonical coordinates.
780     desc.setSupportsMultiResolution(kSupportsMultiResolution);
781     desc.setOverlayInteractDescriptor(new RampOverlayDescriptorOldParams);
782 
783 #ifdef OFX_EXTENSIONS_NATRON
784     desc.setChannelSelector(ePixelComponentNone); // we have our own channel selector
785 #endif
786 }
787 
788 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)789 RampPluginFactory::createInstance(OfxImageEffectHandle handle,
790                                   ContextEnum /*context*/)
791 {
792     return new RampPlugin(handle);
793 }
794 
795 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)796 RampPluginFactory::describeInContext(ImageEffectDescriptor &desc,
797                                      ContextEnum context)
798 {
799     // Source clip only in the filter context
800     // create the mandated source clip
801     // always declare the source clip first, because some hosts may consider
802     // it as the default input clip (e.g. Nuke)
803     ClipDescriptor *srcClip = desc.defineClip(kOfxImageEffectSimpleSourceClipName);
804 
805     srcClip->addSupportedComponent(ePixelComponentRGBA);
806     srcClip->addSupportedComponent(ePixelComponentRGB);
807 #ifdef OFX_EXTENSIONS_NATRON
808     srcClip->addSupportedComponent(ePixelComponentXY);
809 #endif
810     srcClip->addSupportedComponent(ePixelComponentAlpha);
811     srcClip->setTemporalClipAccess(false);
812     srcClip->setSupportsTiles(kSupportsTiles);
813     srcClip->setIsMask(false);
814     srcClip->setOptional(true);
815 
816     // create the mandated output clip
817     ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
818     dstClip->addSupportedComponent(ePixelComponentRGBA);
819     dstClip->addSupportedComponent(ePixelComponentRGB);
820 #ifdef OFX_EXTENSIONS_NATRON
821     dstClip->addSupportedComponent(ePixelComponentXY);
822 #endif
823     dstClip->addSupportedComponent(ePixelComponentAlpha);
824     dstClip->setSupportsTiles(kSupportsTiles);
825 
826     ClipDescriptor *maskClip = (context == eContextPaint) ? desc.defineClip("Brush") : desc.defineClip("Mask");
827     maskClip->addSupportedComponent(ePixelComponentAlpha);
828     maskClip->setTemporalClipAccess(false);
829     if (context != eContextPaint) {
830         maskClip->setOptional(true);
831     }
832     maskClip->setSupportsTiles(kSupportsTiles);
833     maskClip->setIsMask(true);
834 
835     // make some pages and to things in
836     PageParamDescriptor *page = desc.definePageParam("Controls");
837 
838     {
839         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamProcessR);
840         param->setLabel(kParamProcessRLabel);
841         param->setHint(kParamProcessRHint);
842         param->setDefault(true);
843         param->setLayoutHint(eLayoutHintNoNewLine, 1);
844         if (page) {
845             page->addChild(*param);
846         }
847     }
848     {
849         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamProcessG);
850         param->setLabel(kParamProcessGLabel);
851         param->setHint(kParamProcessGHint);
852         param->setDefault(true);
853         param->setLayoutHint(eLayoutHintNoNewLine, 1);
854         if (page) {
855             page->addChild(*param);
856         }
857     }
858     {
859         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamProcessB);
860         param->setLabel(kParamProcessBLabel);
861         param->setHint(kParamProcessBHint);
862         param->setDefault(true);
863         param->setLayoutHint(eLayoutHintNoNewLine, 1);
864         if (page) {
865             page->addChild(*param);
866         }
867     }
868     {
869         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamProcessA);
870         param->setLabel(kParamProcessALabel);
871         param->setHint(kParamProcessAHint);
872         param->setDefault(true);
873         param->setAnimates(false);
874         desc.addClipPreferencesSlaveParam(*param);
875         if (page) {
876             page->addChild(*param);
877         }
878     }
879 
880     ofxsRampDescribeParams(desc, page, NULL, eRampTypeLinear, /*isOpen=*/ true, /*oldParams=*/ true);
881 
882     ofxsMaskMixDescribeParams(desc, page);
883 } // RampPluginFactory::describeInContext
884 
885 static RampPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
886 mRegisterPluginFactoryInstance(p)
887 
888 OFXS_NAMESPACE_ANONYMOUS_EXIT
889