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 Threshold plugin.
21  */
22 
23 #include <cmath>
24 #include <cfloat> // DBL_MAX
25 #include <cstring>
26 #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
27 #include <windows.h>
28 #endif
29 
30 #include "ofxsProcessing.H"
31 #include "ofxsCoords.h"
32 #include "ofxsMacros.h"
33 #ifdef OFX_EXTENSIONS_NATRON
34 #include "ofxNatron.h"
35 #endif
36 #include "ofxsThreadSuite.h"
37 
38 using namespace OFX;
39 
40 OFXS_NAMESPACE_ANONYMOUS_ENTER
41 
42 #define kPluginName "Threshold"
43 #define kPluginGrouping "Color"
44 #define kPluginDescription \
45     "Threshold the selected channels, so that values less than the given Threshold Value become zero, and values greater than or equal become one.\n" \
46     "If the Threshold Softness is nonzero, values less than value-softness become zero, values greater than value+softness become one, and values are linearly interpolated inbetween.\n" \
47     "Note that when thresholding color values with a non-opaque alpha, the color values should in general be unpremultiplied for thresholding."
48 
49 #define STRINGIZE_CPP_NAME_(token) # token
50 #define STRINGIZE_CPP_(token) STRINGIZE_CPP_NAME_(token)
51 
52 #define kPluginIdentifier "net.sf.openfx.Threshold"
53 
54 // History:
55 // version 1.0: initial version
56 #define kPluginVersionMajor 1 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
57 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
58 
59 #define kSupportsTiles 1
60 #define kSupportsMultiResolution 1
61 #define kSupportsRenderScale 1
62 #define kSupportsMultipleClipPARs false
63 #define kSupportsMultipleClipDepths false
64 #define kRenderThreadSafety eRenderFullySafe
65 
66 #ifdef OFX_EXTENSIONS_NATRON
67 #define kParamProcessR kNatronOfxParamProcessR
68 #define kParamProcessRLabel kNatronOfxParamProcessRLabel
69 #define kParamProcessRHint kNatronOfxParamProcessRHint
70 #define kParamProcessG kNatronOfxParamProcessG
71 #define kParamProcessGLabel kNatronOfxParamProcessGLabel
72 #define kParamProcessGHint kNatronOfxParamProcessGHint
73 #define kParamProcessB kNatronOfxParamProcessB
74 #define kParamProcessBLabel kNatronOfxParamProcessBLabel
75 #define kParamProcessBHint kNatronOfxParamProcessBHint
76 #define kParamProcessA kNatronOfxParamProcessA
77 #define kParamProcessALabel kNatronOfxParamProcessALabel
78 #define kParamProcessAHint kNatronOfxParamProcessAHint
79 #else
80 #define kParamProcessR      "processR"
81 #define kParamProcessRLabel "R"
82 #define kParamProcessRHint  "Process red component."
83 #define kParamProcessG      "processG"
84 #define kParamProcessGLabel "G"
85 #define kParamProcessGHint  "Process green component."
86 #define kParamProcessB      "processB"
87 #define kParamProcessBLabel "B"
88 #define kParamProcessBHint  "Process blue component."
89 #define kParamProcessA      "processA"
90 #define kParamProcessALabel "A"
91 #define kParamProcessAHint  "Process alpha component."
92 #endif
93 
94 #define kParamLevelName  "level"
95 #define kParamLevelLabel "Threshold Level"
96 #define kParamLevelHint  "Threshold level for the selected channels."
97 
98 #define kParamSoftnessName  "softness"
99 #define kParamSoftnessLabel "Threshold Softness"
100 #define kParamSoftnessHint  "Threshold softness for the selected channels."
101 
102 #ifdef OFX_EXTENSIONS_NATRON
103 #define OFX_COMPONENTS_OK(c) ((c)== ePixelComponentAlpha || (c) == ePixelComponentXY || (c) == ePixelComponentRGB || (c) == ePixelComponentRGBA)
104 #else
105 #define OFX_COMPONENTS_OK(c) ((c)== ePixelComponentAlpha || (c) == ePixelComponentRGB || (c) == ePixelComponentRGBA)
106 #endif
107 
108 
109 struct RGBAValues
110 {
111     double r, g, b, a;
RGBAValuesRGBAValues112     RGBAValues(double v) : r(v), g(v), b(v), a(v) {}
113 
RGBAValuesRGBAValues114     RGBAValues() : r(0), g(0), b(0), a(0) {}
115 };
116 
117 class ThresholdProcessorBase
118     : public ImageProcessor
119 {
120 protected:
121     const Image *_srcImg;
122     bool _processR;
123     bool _processG;
124     bool _processB;
125     bool _processA;
126     RGBAValues _level;
127     RGBAValues _softness;
128 
129 public:
130 
ThresholdProcessorBase(ImageEffect & instance)131     ThresholdProcessorBase(ImageEffect &instance)
132         : ImageProcessor(instance)
133         , _srcImg(NULL)
134         , _processR(true)
135         , _processG(true)
136         , _processB(true)
137         , _processA(false)
138         , _level()
139         , _softness()
140     {
141     }
142 
setSrcImg(const Image * v)143     void setSrcImg(const Image *v) {_srcImg = v; }
144 
setValues(bool processR,bool processG,bool processB,bool processA,const RGBAValues & level,const RGBAValues & softness)145     void setValues(bool processR,
146                    bool processG,
147                    bool processB,
148                    bool processA,
149                    const RGBAValues& level,
150                    const RGBAValues& softness)
151     {
152         _processR = processR;
153         _processG = processG;
154         _processB = processB;
155         _processA = processA;
156         _level = level;
157         _softness = softness;
158     }
159 
160 private:
161 };
162 
163 
164 template <class PIX, int nComponents, int maxValue>
165 class ThresholdProcessor
166     : public ThresholdProcessorBase
167 {
168 public:
ThresholdProcessor(ImageEffect & instance)169     ThresholdProcessor(ImageEffect &instance)
170         : ThresholdProcessorBase(instance)
171     {
172     }
173 
174 private:
175 
multiThreadProcessImages(OfxRectI procWindow)176     void multiThreadProcessImages(OfxRectI procWindow)
177     {
178 #     ifndef __COVERITY__ // too many coverity[dead_error_line] errors
179         const bool r = _processR && (nComponents != 1);
180         const bool g = _processG && (nComponents >= 2);
181         const bool b = _processB && (nComponents >= 3);
182         const bool a = _processA && (nComponents == 1 || nComponents == 4);
183         if (r) {
184             if (g) {
185                 if (b) {
186                     if (a) {
187                         return process<true, true, true, true >(procWindow); // RGBA
188                     } else {
189                         return process<true, true, true, false>(procWindow); // RGBa
190                     }
191                 } else {
192                     if (a) {
193                         return process<true, true, false, true >(procWindow); // RGbA
194                     } else {
195                         return process<true, true, false, false>(procWindow); // RGba
196                     }
197                 }
198             } else {
199                 if (b) {
200                     if (a) {
201                         return process<true, false, true, true >(procWindow); // RgBA
202                     } else {
203                         return process<true, false, true, false>(procWindow); // RgBa
204                     }
205                 } else {
206                     if (a) {
207                         return process<true, false, false, true >(procWindow); // RgbA
208                     } else {
209                         return process<true, false, false, false>(procWindow); // Rgba
210                     }
211                 }
212             }
213         } else {
214             if (g) {
215                 if (b) {
216                     if (a) {
217                         return process<false, true, true, true >(procWindow); // rGBA
218                     } else {
219                         return process<false, true, true, false>(procWindow); // rGBa
220                     }
221                 } else {
222                     if (a) {
223                         return process<false, true, false, true >(procWindow); // rGbA
224                     } else {
225                         return process<false, true, false, false>(procWindow); // rGba
226                     }
227                 }
228             } else {
229                 if (b) {
230                     if (a) {
231                         return process<false, false, true, true >(procWindow); // rgBA
232                     } else {
233                         return process<false, false, true, false>(procWindow); // rgBa
234                     }
235                 } else {
236                     if (a) {
237                         return process<false, false, false, true >(procWindow); // rgbA
238                     } else {
239                         return process<false, false, false, false>(procWindow); // rgba
240                     }
241                 }
242             }
243         }
244 #     endif // ifndef __COVERITY__
245     } // multiThreadProcessImages
246 
threshold(PIX value,double low,double high)247     PIX threshold(PIX value, double low, double high)
248     {
249         if (value <= low * maxValue) {
250           return (PIX)0;
251         }
252         if (value >= high * maxValue) {
253           return (PIX)maxValue;
254         }
255         return (PIX)((value - low * maxValue) / (high - low) + (maxValue == 1 ? 0. : 0.5));
256     }
257 
258     template<bool processR, bool processG, bool processB, bool processA>
process(const OfxRectI & procWindow)259     void process(const OfxRectI& procWindow)
260     {
261         assert(nComponents == 1 || nComponents == 3 || nComponents == 4);
262         assert(_dstImg);
263         for (int y = procWindow.y1; y < procWindow.y2; y++) {
264             if ( _effect.abort() ) {
265                 break;
266             }
267 
268             PIX *dstPix = (PIX *) _dstImg->getPixelAddress(procWindow.x1, y);
269 
270             for (int x = procWindow.x1; x < procWindow.x2; x++) {
271                 const PIX *srcPix = (const PIX *)  (_srcImg ? _srcImg->getPixelAddress(x, y) : 0);
272                 if (nComponents == 1) {
273                     if (processA && srcPix) {
274                        dstPix[0] = threshold(srcPix[0], _level.a - _softness.a, _level.a + _softness.a);
275                     } else {
276                        dstPix[0] = srcPix ? srcPix[0] : PIX();
277                     }
278                 } else if ( (nComponents == 3) || (nComponents == 4) ) {
279                     if ( processR && srcPix ) {
280                         dstPix[0] = threshold(srcPix[0], _level.r - _softness.r, _level.r + _softness.r);
281                     } else {
282                         dstPix[0] = srcPix ? srcPix[0] : PIX();
283                     }
284                     if ( processG && srcPix ) {
285                         dstPix[1] = threshold(srcPix[1], _level.g - _softness.g, _level.g + _softness.g);
286                     } else {
287                         dstPix[1] = srcPix ? srcPix[1] : PIX();
288                     }
289                     if ( processB && srcPix ) {
290                         dstPix[2] = threshold(srcPix[2], _level.b - _softness.b, _level.b + _softness.b);
291                     } else {
292                         dstPix[2] = srcPix ? srcPix[2] : PIX();
293                     }
294                     if ( processA && srcPix && nComponents == 4 ) {
295                         dstPix[3] = threshold(srcPix[3], _level.a - _softness.a, _level.a + _softness.a);
296                     } else {
297                         dstPix[3] = srcPix ? srcPix[3] : PIX();
298                     }
299                 }
300                 // increment the dst pixel
301                 dstPix += nComponents;
302             }
303         }
304     } // process
305 };
306 
307 
308 ////////////////////////////////////////////////////////////////////////////////
309 /** @brief The plugin that does our work */
310 class ThresholdPlugin
311     : public ImageEffect
312 {
313 public:
314     /** @brief ctor */
ThresholdPlugin(OfxImageEffectHandle handle)315     ThresholdPlugin(OfxImageEffectHandle handle)
316         : ImageEffect(handle)
317         , _dstClip(NULL)
318         , _srcClip(NULL)
319         , _processR(NULL)
320         , _processG(NULL)
321         , _processB(NULL)
322         , _processA(NULL)
323         , _level(NULL)
324         , _softness(NULL)
325     {
326         _dstClip = fetchClip(kOfxImageEffectOutputClipName);
327         assert( _dstClip && (!_dstClip->isConnected() || OFX_COMPONENTS_OK(_dstClip->getPixelComponents())) );
328         _srcClip = getContext() == eContextGenerator ? NULL : fetchClip(kOfxImageEffectSimpleSourceClipName);
329         assert( (!_srcClip && getContext() == eContextGenerator) ||
330                 ( _srcClip && (!_srcClip->isConnected() || OFX_COMPONENTS_OK(_srcClip->getPixelComponents())) ) );
331         _processR = fetchBooleanParam(kParamProcessR);
332         _processG = fetchBooleanParam(kParamProcessG);
333         _processB = fetchBooleanParam(kParamProcessB);
334         _processA = fetchBooleanParam(kParamProcessA);
335         assert(_processR && _processG && _processB && _processA);
336         _level = fetchRGBAParam(kParamLevelName);
337         _softness = fetchRGBAParam(kParamSoftnessName);
338         assert(_level && _softness);
339     }
340 
341 private:
342     /* Override the render */
343     virtual void render(const RenderArguments &args) OVERRIDE FINAL;
344 
345     template <int nComponents>
346     void renderInternal(const RenderArguments &args, BitDepthEnum dstBitDepth);
347 
348     /* set up and run a processor */
349     void setupAndProcess(ThresholdProcessorBase &, const RenderArguments &args);
350 
351     virtual bool isIdentity(const IsIdentityArguments &args, Clip * &identityClip, double &identityTime, int& view, std::string& plane) OVERRIDE FINAL;
352 
353 private:
354     // do not need to delete these, the ImageEffect is managing them for us
355     Clip *_dstClip;
356     Clip *_srcClip;
357     BooleanParam* _processR;
358     BooleanParam* _processG;
359     BooleanParam* _processB;
360     BooleanParam* _processA;
361     RGBAParam *_level;
362     RGBAParam *_softness;
363 };
364 
365 
366 ////////////////////////////////////////////////////////////////////////////////
367 /** @brief render for the filter */
368 
369 ////////////////////////////////////////////////////////////////////////////////
370 // basic plugin render function, just a skelington to instantiate templates from
371 
372 /* set up and run a processor */
373 void
setupAndProcess(ThresholdProcessorBase & processor,const RenderArguments & args)374 ThresholdPlugin::setupAndProcess(ThresholdProcessorBase &processor,
375                            const RenderArguments &args)
376 {
377     auto_ptr<Image> dst( _dstClip->fetchImage(args.time) );
378 
379     if ( !dst.get() ) {
380         throwSuiteStatusException(kOfxStatFailed);
381     }
382     BitDepthEnum dstBitDepth    = dst->getPixelDepth();
383     PixelComponentEnum dstComponents  = dst->getPixelComponents();
384     if ( ( dstBitDepth != _dstClip->getPixelDepth() ) ||
385          ( dstComponents != _dstClip->getPixelComponents() ) ) {
386         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong depth or components");
387         throwSuiteStatusException(kOfxStatFailed);
388     }
389     if ( (dst->getRenderScale().x != args.renderScale.x) ||
390          ( dst->getRenderScale().y != args.renderScale.y) ||
391          ( ( dst->getField() != eFieldNone) /* for DaVinci Resolve */ && ( dst->getField() != args.fieldToRender) ) ) {
392         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
393         throwSuiteStatusException(kOfxStatFailed);
394     }
395     auto_ptr<const Image> src( ( _srcClip && _srcClip->isConnected() ) ?
396                                     _srcClip->fetchImage(args.time) : 0 );
397     if ( src.get() ) {
398         BitDepthEnum srcBitDepth      = src->getPixelDepth();
399         PixelComponentEnum srcComponents = src->getPixelComponents();
400         if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
401             throwSuiteStatusException(kOfxStatErrImageFormat);
402         }
403     }
404     // set the images
405     processor.setDstImg( dst.get() );
406     processor.setSrcImg( src.get() );
407     // set the render window
408     processor.setRenderWindow(args.renderWindow);
409 
410     bool processR, processG, processB, processA;
411     _processR->getValueAtTime(args.time, processR);
412     _processG->getValueAtTime(args.time, processG);
413     _processB->getValueAtTime(args.time, processB);
414     _processA->getValueAtTime(args.time, processA);
415     RGBAValues level;
416     _level->getValueAtTime(args.time, level.r, level.g, level.b, level.a);
417     RGBAValues softness;
418     _softness->getValueAtTime(args.time, softness.r, softness.g, softness.b, softness.a);
419     processor.setValues(processR, processG, processB, processA,
420                         level, softness);
421 
422     // Call the base class process member, this will call the derived templated process code
423     processor.process();
424 } // ThresholdPlugin::setupAndProcess
425 
426 // the internal render function
427 template <int nComponents>
428 void
renderInternal(const RenderArguments & args,BitDepthEnum dstBitDepth)429 ThresholdPlugin::renderInternal(const RenderArguments &args,
430                           BitDepthEnum dstBitDepth)
431 {
432     switch (dstBitDepth) {
433     case eBitDepthUByte: {
434         ThresholdProcessor<unsigned char, nComponents, 255> fred(*this);
435         setupAndProcess(fred, args);
436         break;
437     }
438     case eBitDepthUShort: {
439         ThresholdProcessor<unsigned short, nComponents, 65536> fred(*this);
440         setupAndProcess(fred, args);
441         break;
442     }
443     case eBitDepthFloat: {
444         ThresholdProcessor<float, nComponents, 1> fred(*this);
445         setupAndProcess(fred, args);
446         break;
447     }
448     default:
449         throwSuiteStatusException(kOfxStatErrUnsupported);
450     }
451 }
452 
453 // the overridden render function
454 void
render(const RenderArguments & args)455 ThresholdPlugin::render(const RenderArguments &args)
456 {
457     // instantiate the render code based on the pixel depth of the dst clip
458     BitDepthEnum dstBitDepth    = _dstClip->getPixelDepth();
459     PixelComponentEnum dstComponents  = _dstClip->getPixelComponents();
460 
461     assert( kSupportsMultipleClipPARs   || !_srcClip || _srcClip->getPixelAspectRatio() == _dstClip->getPixelAspectRatio() );
462     assert( kSupportsMultipleClipDepths || !_srcClip || _srcClip->getPixelDepth()       == _dstClip->getPixelDepth() );
463     assert(OFX_COMPONENTS_OK(dstComponents));
464     if (dstComponents == ePixelComponentRGBA) {
465         renderInternal<4>(args, dstBitDepth);
466     } else if (dstComponents == ePixelComponentRGB) {
467         renderInternal<3>(args, dstBitDepth);
468 #ifdef OFX_EXTENSIONS_NATRON
469     } else if (dstComponents == ePixelComponentXY) {
470         renderInternal<2>(args, dstBitDepth);
471 #endif
472     } else {
473         assert(dstComponents == ePixelComponentAlpha);
474         renderInternal<1>(args, dstBitDepth);
475     }
476 }
477 
478 bool
isIdentity(const IsIdentityArguments & args,Clip * & identityClip,double &,int &,std::string &)479 ThresholdPlugin::isIdentity(const IsIdentityArguments &args,
480                             Clip * &identityClip,
481                             double & /*identityTime*/,
482                             int & /*identityView*/,
483                             std::string& /*identityPlane*/)
484 {
485     {
486         bool processR, processG, processB, processA;
487         _processR->getValueAtTime(args.time, processR);
488         _processG->getValueAtTime(args.time, processG);
489         _processB->getValueAtTime(args.time, processB);
490         _processA->getValueAtTime(args.time, processA);
491         if ( !processR && !processG && !processB && !processA ) {
492             identityClip = _srcClip;
493 
494             return true;
495         }
496     }
497 
498     return false;
499 } // ThresholdPlugin::isIdentity
500 
501 mDeclarePluginFactory(ThresholdPluginFactory, {ofxsThreadSuiteCheck();}, {});
502 void
describe(ImageEffectDescriptor & desc)503 ThresholdPluginFactory::describe(ImageEffectDescriptor &desc)
504 {
505     // basic labels
506     desc.setLabel(kPluginName);
507     desc.setPluginGrouping(kPluginGrouping);
508     desc.setPluginDescription(kPluginDescription);
509 
510     desc.addSupportedContext(eContextFilter);
511     desc.addSupportedContext(eContextGeneral);
512     desc.addSupportedContext(eContextPaint);
513     desc.addSupportedBitDepth(eBitDepthUByte);
514     desc.addSupportedBitDepth(eBitDepthUShort);
515     desc.addSupportedBitDepth(eBitDepthFloat);
516 
517     // set a few flags
518     desc.setSingleInstance(false);
519     desc.setHostFrameThreading(false);
520     desc.setSupportsMultiResolution(kSupportsMultiResolution);
521     desc.setSupportsTiles(kSupportsTiles);
522     desc.setTemporalClipAccess(false);
523     desc.setRenderTwiceAlways(false);
524     desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
525     desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);
526     desc.setRenderThreadSafety(kRenderThreadSafety);
527 
528 #ifdef OFX_EXTENSIONS_NATRON
529     desc.setChannelSelector(ePixelComponentNone); // we have our own channel selector
530 #endif
531 }
532 
533 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum)534 ThresholdPluginFactory::describeInContext(ImageEffectDescriptor &desc,
535                                           ContextEnum /*context*/)
536 {
537     // Source clip only in the filter context
538     // create the mandated source clip
539     ClipDescriptor *srcClip = desc.defineClip(kOfxImageEffectSimpleSourceClipName);
540 
541     srcClip->addSupportedComponent(ePixelComponentRGBA);
542     srcClip->addSupportedComponent(ePixelComponentRGB);
543     srcClip->addSupportedComponent(ePixelComponentAlpha);
544     srcClip->setTemporalClipAccess(false);
545     srcClip->setSupportsTiles(kSupportsTiles);
546     srcClip->setIsMask(false);
547 
548     // create the mandated output clip
549     ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
550     dstClip->addSupportedComponent(ePixelComponentRGBA);
551     dstClip->addSupportedComponent(ePixelComponentRGB);
552     dstClip->addSupportedComponent(ePixelComponentAlpha);
553     dstClip->setSupportsTiles(kSupportsTiles);
554 
555     // make some pages and to things in
556     PageParamDescriptor *page = desc.definePageParam("Controls");
557 
558     {
559         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamProcessR);
560         param->setLabel(kParamProcessRLabel);
561         param->setHint(kParamProcessRHint);
562         param->setDefault(true);
563         param->setLayoutHint(eLayoutHintNoNewLine, 1);
564         if (page) {
565             page->addChild(*param);
566         }
567     }
568     {
569         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamProcessG);
570         param->setLabel(kParamProcessGLabel);
571         param->setHint(kParamProcessGHint);
572         param->setDefault(true);
573         param->setLayoutHint(eLayoutHintNoNewLine, 1);
574         if (page) {
575             page->addChild(*param);
576         }
577     }
578     {
579         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamProcessB);
580         param->setLabel(kParamProcessBLabel);
581         param->setHint(kParamProcessBHint);
582         param->setDefault(true);
583         param->setLayoutHint(eLayoutHintNoNewLine, 1);
584         if (page) {
585             page->addChild(*param);
586         }
587     }
588     {
589         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamProcessA);
590         param->setLabel(kParamProcessALabel);
591         param->setHint(kParamProcessAHint);
592         param->setDefault(true);
593         if (page) {
594             page->addChild(*param);
595         }
596     }
597 
598     {
599         RGBAParamDescriptor *param = desc.defineRGBAParam(kParamLevelName);
600         param->setLabel(kParamLevelLabel);
601         param->setHint(kParamLevelHint);
602         param->setDefault(0.0, 0.0, 0.0, 0.0);
603         param->setRange(-DBL_MAX, -DBL_MAX, -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX, DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
604         param->setDisplayRange(-1, -1, -1, -1, 1, 1, 1, 1);
605         param->setAnimates(true); // can animate
606         if (page) {
607             page->addChild(*param);
608         }
609     }
610     {
611         RGBAParamDescriptor *param = desc.defineRGBAParam(kParamSoftnessName);
612         param->setLabel(kParamSoftnessLabel);
613         param->setHint(kParamSoftnessHint);
614         param->setDefault(0.0, 0.0, 0.0, 0.0);
615         param->setRange(0., 0., 0., 0., DBL_MAX, DBL_MAX, DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
616         param->setDisplayRange(0, 0, 0, 0, 1, 1, 1, 1);
617         param->setAnimates(true); // can animate
618         if (page) {
619             page->addChild(*param);
620         }
621     }
622 } // ThresholdPluginFactory::describeInContext
623 
624 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)625 ThresholdPluginFactory::createInstance(OfxImageEffectHandle handle,
626                                  ContextEnum /*context*/)
627 {
628     return new ThresholdPlugin(handle);
629 }
630 
631 static ThresholdPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
632 mRegisterPluginFactoryInstance(p)
633 
634 OFXS_NAMESPACE_ANONYMOUS_EXIT
635