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