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