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 TimeBlur plugin.
21  */
22 
23 #include <cmath> // for floor
24 #include <climits> // for INT_MAX
25 #include <cassert>
26 #include <algorithm>
27 #ifdef DEBUG
28 #include <cstdio>
29 #endif
30 
31 #include "ofxsImageEffect.h"
32 #include "ofxsThreadSuite.h"
33 #include "ofxsMultiThread.h"
34 
35 #include "ofxsPixelProcessor.h"
36 #include "ofxsCoords.h"
37 #include "ofxsShutter.h"
38 #include "ofxsMaskMix.h"
39 #include "ofxsMacros.h"
40 
41 using namespace OFX;
42 
43 OFXS_NAMESPACE_ANONYMOUS_ENTER
44 
45 #define kPluginName "TimeBlurOFX"
46 #define kPluginGrouping "Time"
47 #define kPluginDescription \
48     "Blend frames of the input clip over the shutter range."
49 
50 #define kPluginDescriptionNuke \
51 " Note that this effect does not work correctly in Nuke, because frames cannot be fetched at fractional times."
52 
53 #define kPluginIdentifier "net.sf.openfx.TimeBlur"
54 // History:
55 // version 1.0: initial version
56 // version 2.0: use kNatronOfxParamProcess* parameters
57 #define kPluginVersionMajor 1 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
58 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
59 
60 #define kSupportsTiles 1
61 #define kSupportsMultiResolution 1
62 #define kSupportsRenderScale 1
63 #define kSupportsMultipleClipPARs false
64 #define kSupportsMultipleClipDepths false
65 #define kRenderThreadSafety eRenderFullySafe
66 
67 #define kParamDivisions     "division"
68 #define kParamDivisionsLabel "Divisions"
69 #define kParamDivisionsHint  "Number of time samples along the shutter time. The first frame is always at the start of the shutter range, and the shutter range is divided by divisions. The frame corresponding to the end of the shutter range is not included. If divisions=4, Shutter=1, Shutter Offset=Centered, this leads to blending the frames at t-0.5, t-0.25, t, t+0.25."
70 
71 #define kFrameChunk 4 // how many frames to process simultaneously
72 
73 #ifdef OFX_EXTENSIONS_NATRON
74 #define OFX_COMPONENTS_OK(c) ((c)== ePixelComponentAlpha || (c) == ePixelComponentXY || (c) == ePixelComponentRGB || (c) == ePixelComponentRGBA)
75 #else
76 #define OFX_COMPONENTS_OK(c) ((c)== ePixelComponentAlpha || (c) == ePixelComponentRGB || (c) == ePixelComponentRGBA)
77 #endif
78 
79 
80 class TimeBlurProcessorBase
81     : public PixelProcessor
82 {
83 protected:
84     std::vector<const Image*> _srcImgs;
85     float *_accumulatorData;
86     int _divisions; // 0 for all passes except the last one
87 
88 public:
89 
TimeBlurProcessorBase(ImageEffect & instance)90     TimeBlurProcessorBase(ImageEffect &instance)
91         : PixelProcessor(instance)
92         , _srcImgs()
93         , _accumulatorData(NULL)
94         , _divisions(0)
95     {
96     }
97 
setSrcImgs(const std::vector<const Image * > & v)98     void setSrcImgs(const std::vector<const Image*> &v) {_srcImgs = v; }
99 
setAccumulator(float * accumulatorData)100     void setAccumulator(float *accumulatorData) {_accumulatorData = accumulatorData; }
101 
setValues(int divisions)102     void setValues(int divisions)
103     {
104         _divisions = divisions;
105     }
106 
107 private:
108 };
109 
110 
111 template <class PIX, int nComponents, int maxValue>
112 class TimeBlurProcessor
113     : public TimeBlurProcessorBase
114 {
115 public:
TimeBlurProcessor(ImageEffect & instance)116     TimeBlurProcessor(ImageEffect &instance)
117         : TimeBlurProcessorBase(instance)
118     {
119     }
120 
121 private:
122 
multiThreadProcessImages(OfxRectI procWindow)123     void multiThreadProcessImages(OfxRectI procWindow)
124     {
125         assert(1 <= nComponents && nComponents <= 4);
126         assert(!_divisions || _dstPixelData);
127         float tmpPix[nComponents];
128         const float initVal = 0.;
129         const bool lastPass = (_divisions != 0);
130         for (int y = procWindow.y1; y < procWindow.y2; y++) {
131             if ( _effect.abort() ) {
132                 break;
133             }
134 
135             PIX *dstPix = lastPass ? (PIX *) getDstPixelAddress(procWindow.x1, y) : 0;
136             assert(!lastPass || dstPix);
137             if (lastPass && !dstPix) {
138                 // coverity[dead_error_line]
139                 continue;
140             }
141 
142             for (int x = procWindow.x1; x < procWindow.x2; x++) {
143                 size_t renderPix = ( (_renderWindow.x2 - _renderWindow.x1) * (y - _renderWindow.y1) +
144                                      (x - _renderWindow.x1) );
145                 if (_accumulatorData) {
146                     std::copy(&_accumulatorData[renderPix * nComponents], &_accumulatorData[renderPix * nComponents + nComponents], tmpPix);
147                 } else {
148                     std::fill(tmpPix, tmpPix + nComponents, initVal);
149                 }
150                 // accumulate
151                 for (unsigned i = 0; i < _srcImgs.size(); ++i) {
152                     const PIX *srcPixi = (const PIX *)  (_srcImgs[i] ? _srcImgs[i]->getPixelAddress(x, y) : 0);
153                     if (srcPixi) {
154                         for (int c = 0; c < nComponents; ++c) {
155                             tmpPix[c] += srcPixi[c];
156                         }
157                     }
158                 }
159                 if (!lastPass) {
160                     assert(_accumulatorData);
161                     if (_accumulatorData) {
162                         std::copy(tmpPix, tmpPix + nComponents, &_accumulatorData[renderPix * nComponents]);
163                     }
164                 } else {
165                     for (int c = 0; c < nComponents; ++c) {
166                         float v = tmpPix[c] / _divisions;
167                         dstPix[c] = ofxsClampIfInt<PIX, maxValue>(v, 0, maxValue);
168                     }
169                     // increment the dst pixel
170                     dstPix += nComponents;
171                 }
172             }
173         }
174     } // multiThreadProcessImages
175 };
176 
177 
178 ////////////////////////////////////////////////////////////////////////////////
179 /** @brief The plugin that does our work */
180 class TimeBlurPlugin
181     : public ImageEffect
182 {
183 public:
184     /** @brief ctor */
TimeBlurPlugin(OfxImageEffectHandle handle)185     TimeBlurPlugin(OfxImageEffectHandle handle)
186         : ImageEffect(handle)
187         , _dstClip(NULL)
188         , _srcClip(NULL)
189         , _divisions(NULL)
190         , _shutter(NULL)
191         , _shutteroffset(NULL)
192         , _shuttercustomoffset(NULL)
193     {
194         _dstClip = fetchClip(kOfxImageEffectOutputClipName);
195         assert( _dstClip && (!_dstClip->isConnected() || OFX_COMPONENTS_OK(_dstClip->getPixelComponents())) );
196         _srcClip = getContext() == eContextGenerator ? NULL : fetchClip(kOfxImageEffectSimpleSourceClipName);
197         assert( (!_srcClip && getContext() == eContextGenerator) ||
198                 ( _srcClip && (!_srcClip->isConnected() || OFX_COMPONENTS_OK(_srcClip->getPixelComponents()) ) ) );
199         _divisions = fetchIntParam(kParamDivisions);
200         _shutter = fetchDoubleParam(kParamShutter);
201         _shutteroffset = fetchChoiceParam(kParamShutterOffset);
202         _shuttercustomoffset = fetchDoubleParam(kParamShutterCustomOffset);
203         assert(_divisions && _shutter && _shutteroffset && _shuttercustomoffset);
204     }
205 
206 private:
207     /* Override the render */
208     virtual void render(const RenderArguments &args) OVERRIDE FINAL;
209 
210     /* set up and run a processor */
211     void setupAndProcess(TimeBlurProcessorBase &, const RenderArguments &args);
212 
213     virtual bool isIdentity(const IsIdentityArguments &args, Clip * &identityClip, double &identityTime, int& view, std::string& plane) OVERRIDE FINAL;
214 
215     /** Override the get frames needed action */
216     virtual void getFramesNeeded(const FramesNeededArguments &args, FramesNeededSetter &frames) OVERRIDE FINAL;
217     virtual bool getRegionOfDefinition(const RegionOfDefinitionArguments &args, OfxRectD &rod) OVERRIDE FINAL;
218 
219 private:
220 
221     template<int nComponents>
222     void renderForComponents(const RenderArguments &args);
223 
224     template <class PIX, int nComponents, int maxValue>
225     void renderForBitDepth(const RenderArguments &args);
226 
227     // do not need to delete these, the ImageEffect is managing them for us
228     Clip *_dstClip;
229     Clip *_srcClip;
230     IntParam* _divisions;
231     DoubleParam* _shutter;
232     ChoiceParam* _shutteroffset;
233     DoubleParam* _shuttercustomoffset;
234 };
235 
236 
237 ////////////////////////////////////////////////////////////////////////////////
238 /** @brief render for the filter */
239 
240 ////////////////////////////////////////////////////////////////////////////////
241 // basic plugin render function, just a skelington to instantiate templates from
242 
243 // Since we cannot hold a auto_ptr in the vector we must hold a raw pointer.
244 // To ensure that images are always freed even in case of exceptions, use a RAII class.
245 struct OptionalImagesHolder_RAII
246 {
247     std::vector<const Image*> images;
248 
OptionalImagesHolder_RAIIOptionalImagesHolder_RAII249     OptionalImagesHolder_RAII()
250         : images()
251     {
252     }
253 
~OptionalImagesHolder_RAIIOptionalImagesHolder_RAII254     ~OptionalImagesHolder_RAII()
255     {
256         for (unsigned int i = 0; i < images.size(); ++i) {
257             delete images[i];
258         }
259     }
260 };
261 
262 /* set up and run a processor */
263 void
setupAndProcess(TimeBlurProcessorBase & processor,const RenderArguments & args)264 TimeBlurPlugin::setupAndProcess(TimeBlurProcessorBase &processor,
265                                 const RenderArguments &args)
266 {
267     const double time = args.time;
268 
269     auto_ptr<Image> dst( _dstClip->fetchImage(time) );
270 
271     if ( !dst.get() ) {
272         throwSuiteStatusException(kOfxStatFailed);
273     }
274     BitDepthEnum dstBitDepth    = dst->getPixelDepth();
275     PixelComponentEnum dstComponents  = dst->getPixelComponents();
276     if ( ( dstBitDepth != _dstClip->getPixelDepth() ) ||
277          ( dstComponents != _dstClip->getPixelComponents() ) ) {
278         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong depth or components");
279         throwSuiteStatusException(kOfxStatFailed);
280     }
281     if ( (dst->getRenderScale().x != args.renderScale.x) ||
282          ( dst->getRenderScale().y != args.renderScale.y) ||
283          ( ( dst->getField() != eFieldNone) /* for DaVinci Resolve */ && ( dst->getField() != args.fieldToRender) ) ) {
284         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
285         throwSuiteStatusException(kOfxStatFailed);
286     }
287 
288     // accumulator image
289     auto_ptr<ImageMemory> accumulator;
290     float *accumulatorData = NULL;
291 
292     // compute range
293     double shutter = _shutter->getValueAtTime(time);
294     ShutterOffsetEnum shutteroffset = (ShutterOffsetEnum)_shutteroffset->getValueAtTime(time);
295     double shuttercustomoffset = _shuttercustomoffset->getValueAtTime(time);
296     OfxRangeD range;
297     shutterRange(time, shutter, shutteroffset, shuttercustomoffset, &range);
298     int divisions = _divisions->getValueAtTime(time);
299     double interval = divisions >= 1 ? (range.max - range.min) / divisions : 1.;
300     const OfxRectI& renderWindow = args.renderWindow;
301     size_t nPixels = (renderWindow.y2 - renderWindow.y1) * (renderWindow.x2 - renderWindow.x1);
302 
303     // Main processing loop.
304     // We process the frame range by chunks, to avoid using too much memory.
305     //
306     // Note that Nuke has a bug in TimeBlur when divisions=1:
307     // -the RoD is the expected RoD from the beginning of the shutter time
308     // - the image is always identity
309     // We chose not to reproduce this bug: when divisions = 1 both the RoD
310     // and the image correspond to the start of shutter time.
311 
312     int imin;
313     int imax = 0;
314     const int n = divisions;
315     while (imax < n) {
316         imin = imax;
317         imax = std::min(imin + kFrameChunk, n);
318         bool lastPass = (imax == n);
319 
320         if (!lastPass) {
321             // Initialize accumulator image (always use float)
322             if (!accumulatorData) {
323                 int dstNComponents = _dstClip->getPixelComponentCount();
324                 accumulator.reset( new ImageMemory(nPixels * dstNComponents * sizeof(float), this) );
325                 accumulatorData = (float*)accumulator->lock();
326                 std::fill(accumulatorData, accumulatorData + nPixels * dstNComponents, 0.);
327             }
328         }
329 
330         // fetch the source images
331         OptionalImagesHolder_RAII srcImgs;
332         for (int i = imin; i < imax; ++i) {
333             if ( abort() ) {
334                 return;
335             }
336             const Image* src = _srcClip ? _srcClip->fetchImage(range.min + i * interval) : 0;
337             //std::printf("TimeBlur: fetchimage(%g)\n", range.min + i * interval);
338             if (src) {
339                 if ( (src->getRenderScale().x != args.renderScale.x) ||
340                      ( src->getRenderScale().y != args.renderScale.y) ||
341                      ( ( src->getField() != eFieldNone) /* for DaVinci Resolve */ && ( src->getField() != args.fieldToRender) ) ) {
342                     setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
343                     throwSuiteStatusException(kOfxStatFailed);
344                 }
345                 BitDepthEnum srcBitDepth      = src->getPixelDepth();
346                 PixelComponentEnum srcComponents = src->getPixelComponents();
347                 if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
348                     throwSuiteStatusException(kOfxStatErrImageFormat);
349                 }
350             }
351             srcImgs.images.push_back(src);
352         }
353 
354         // set the images
355         if (lastPass) {
356             processor.setDstImg( dst.get() );
357         }
358         processor.setSrcImgs(srcImgs.images);
359         // set the render window
360         processor.setRenderWindow(renderWindow);
361         processor.setAccumulator(accumulatorData);
362 
363         processor.setValues(lastPass ? divisions : 0);
364 
365         // Call the base class process member, this will call the derived templated process code
366         processor.process();
367     }
368 } // TimeBlurPlugin::setupAndProcess
369 
370 // the overridden render function
371 void
render(const RenderArguments & args)372 TimeBlurPlugin::render(const RenderArguments &args)
373 {
374     PixelComponentEnum dstComponents  = _dstClip->getPixelComponents();
375 
376     assert( kSupportsMultipleClipPARs   || !_srcClip || _srcClip->getPixelAspectRatio() == _dstClip->getPixelAspectRatio() );
377     assert( kSupportsMultipleClipDepths || !_srcClip || _srcClip->getPixelDepth()       == _dstClip->getPixelDepth() );
378     assert(OFX_COMPONENTS_OK(dstComponents));
379     if (dstComponents == ePixelComponentRGBA) {
380         renderForComponents<4>(args);
381     } else if (dstComponents == ePixelComponentAlpha) {
382         renderForComponents<1>(args);
383 #ifdef OFX_EXTENSIONS_NATRON
384     } else if (dstComponents == ePixelComponentXY) {
385         renderForComponents<2>(args);
386 #endif
387     } else {
388         assert(dstComponents == ePixelComponentRGB);
389         renderForComponents<3>(args);
390     }
391 }
392 
393 template<int nComponents>
394 void
renderForComponents(const RenderArguments & args)395 TimeBlurPlugin::renderForComponents(const RenderArguments &args)
396 {
397     BitDepthEnum dstBitDepth    = _dstClip->getPixelDepth();
398 
399     switch (dstBitDepth) {
400     case eBitDepthUByte:
401         renderForBitDepth<unsigned char, nComponents, 255>(args);
402         break;
403 
404     case eBitDepthUShort:
405         renderForBitDepth<unsigned short, nComponents, 65535>(args);
406         break;
407 
408     case eBitDepthFloat:
409         renderForBitDepth<float, nComponents, 1>(args);
410         break;
411     default:
412         throwSuiteStatusException(kOfxStatErrUnsupported);
413     }
414 }
415 
416 template <class PIX, int nComponents, int maxValue>
417 void
renderForBitDepth(const RenderArguments & args)418 TimeBlurPlugin::renderForBitDepth(const RenderArguments &args)
419 {
420     TimeBlurProcessor<PIX, nComponents, maxValue> fred(*this);
421     setupAndProcess(fred, args);
422 }
423 
424 bool
isIdentity(const IsIdentityArguments & args,Clip * & identityClip,double & identityTime,int &,std::string &)425 TimeBlurPlugin::isIdentity(const IsIdentityArguments &args,
426                            Clip * &identityClip,
427                            double &identityTime
428                            , int& /*view*/, std::string& /*plane*/)
429 {
430     const double time = args.time;
431 
432     // compute range
433     double shutter = 0.;
434 
435     _shutter->getValueAtTime(time, shutter);
436     if (shutter != 0) {
437         int divisions;
438         _divisions->getValueAtTime(time, divisions);
439         if (divisions > 1) {
440             return false;
441         }
442     }
443     ShutterOffsetEnum shutteroffset_i = (ShutterOffsetEnum)_shutteroffset->getValueAtTime(time);
444     double shuttercustomoffset = _shuttercustomoffset->getValueAtTime(time);
445     OfxRangeD range;
446     shutterRange(time, shutter, (ShutterOffsetEnum)shutteroffset_i, shuttercustomoffset, &range);
447 
448     // Note that Nuke has a bug in TimeBlur when divisions=1:
449     // -the RoD is the expected RoD from the beginning of the shutter time
450     // - the image is always identity
451     // We chose not to reproduce this bug: when divisions = 1 both the RoD
452     // and the image correspond to the start of shutter time.
453     //
454     identityClip = _srcClip;
455     identityTime = range.min;
456 
457     return true;
458 }
459 
460 void
getFramesNeeded(const FramesNeededArguments & args,FramesNeededSetter & frames)461 TimeBlurPlugin::getFramesNeeded(const FramesNeededArguments &args,
462                                 FramesNeededSetter &frames)
463 {
464     const double time = args.time;
465     // compute range
466     double shutter = _shutter->getValueAtTime(time);
467     ShutterOffsetEnum shutteroffset_i = (ShutterOffsetEnum)_shutteroffset->getValueAtTime(time);
468     double shuttercustomoffset = _shuttercustomoffset->getValueAtTime(time);
469     OfxRangeD range;
470 
471     // Note that Nuke has a bug in TimeBlur when divisions=1:
472     // -the RoD is the expected RoD from the beginning of the shutter time
473     // - the image is always identity
474     // We chose not to reproduce this bug: when divisions = 1 both the RoD
475     // and the image correspond to the start of shutter time.
476 
477     shutterRange(time, shutter, (ShutterOffsetEnum)shutteroffset_i, shuttercustomoffset, &range);
478     int divisions = _divisions->getValueAtTime(time);
479 
480     if ( (shutter == 0) || (divisions <= 1) ) {
481         range.max = range.min;
482         frames.setFramesNeeded(*_srcClip, range);
483 
484         return;
485     }
486 
487     //#define OFX_HOST_ACCEPTS_FRACTIONAL_FRAME_RANGES // works with Natron, but this is perhaps borderline with respect to OFX spec
488     // Edit: Natron works better if you input the same range that what is going to be done in render.
489 #ifdef OFX_HOST_ACCEPTS_FRACTIONAL_FRAME_RANGES
490     //std::printf("TimeBlur: range(%g,%g)\n", range.min, range.max);
491     frames.setFramesNeeded(*_srcClip, range);
492 #else
493     // return the exact list of frames rather than a frame range , so that they can be pre-rendered by the host.
494     double interval = (range.max - range.min) / divisions;
495     for (int i = 0; i < divisions; ++i) {
496         double t = range.min + i * interval;
497         OfxRangeD r = {t, t};
498         //std::printf("TimeBlur: frames for t=%g range(%g,%g) %lx\n", args.time, r.min, r.max, *((unsigned long*)&t));
499         frames.setFramesNeeded(*_srcClip, r);
500     }
501 #endif
502 }
503 
504 bool
getRegionOfDefinition(const RegionOfDefinitionArguments & args,OfxRectD & rod)505 TimeBlurPlugin::getRegionOfDefinition(const RegionOfDefinitionArguments &args,
506                                       OfxRectD &rod)
507 {
508     const double time = args.time;
509     // compute range
510     double shutter = _shutter->getValueAtTime(time);
511     ShutterOffsetEnum shutteroffset = (ShutterOffsetEnum)_shutteroffset->getValueAtTime(time);
512     double shuttercustomoffset = _shuttercustomoffset->getValueAtTime(time);
513     OfxRangeD range;
514 
515     // Compute the RoD as the union of all fetched input's RoDs
516     //
517     // Note that Nuke has a bug in TimeBlur when divisions=1:
518     // -the RoD is the expected RoD from the beginning of the shutter time
519     // - the image is always identity
520     // We chose not to reproduce this bug: when divisions = 1 both the RoD
521     // and the image correspond to the start of shutter time.
522 
523     shutterRange(time, shutter, shutteroffset, shuttercustomoffset, &range);
524     int divisions = _divisions->getValueAtTime(time);
525     double interval = divisions > 1 ? (range.max - range.min) / divisions : 1.;
526 
527     rod = _srcClip->getRegionOfDefinition(range.min);
528 
529     for (int i = 1; i < divisions; ++i) {
530         OfxRectD srcRoD = _srcClip->getRegionOfDefinition(range.min + i * interval);
531         Coords::rectBoundingBox(srcRoD, rod, &rod);
532     }
533 
534     return true;
535 }
536 
537 mDeclarePluginFactory(TimeBlurPluginFactory, {ofxsThreadSuiteCheck();}, {});
538 void
describe(ImageEffectDescriptor & desc)539 TimeBlurPluginFactory::describe(ImageEffectDescriptor &desc)
540 {
541     // basic labels
542     desc.setLabel(kPluginName);
543     desc.setPluginGrouping(kPluginGrouping);
544     std::string description = kPluginDescription;
545     if (getImageEffectHostDescription()->hostName == "uk.co.thefoundry.nuke") {
546         description += kPluginDescriptionNuke;
547     }
548     desc.setPluginDescription(description);
549 
550     desc.addSupportedContext(eContextFilter);
551     desc.addSupportedContext(eContextGeneral);
552     desc.addSupportedBitDepth(eBitDepthUByte);
553     desc.addSupportedBitDepth(eBitDepthUShort);
554     desc.addSupportedBitDepth(eBitDepthFloat);
555 
556     // set a few flags
557     desc.setSingleInstance(false);
558     desc.setHostFrameThreading(false);
559     desc.setSupportsMultiResolution(kSupportsMultiResolution);
560     desc.setSupportsTiles(kSupportsTiles);
561     desc.setTemporalClipAccess(true);
562     desc.setRenderTwiceAlways(false);
563     desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
564     desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);
565     desc.setRenderThreadSafety(kRenderThreadSafety);
566 
567 #ifdef OFX_EXTENSIONS_NATRON
568     //desc.setChannelSelector(ePixelComponentNone); // we have our own channel selector
569 #endif
570 }
571 
572 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)573 TimeBlurPluginFactory::describeInContext(ImageEffectDescriptor &desc,
574                                          ContextEnum context)
575 {
576     // Source clip only in the filter context
577     // create the mandated source clip
578     ClipDescriptor *srcClip = desc.defineClip(kOfxImageEffectSimpleSourceClipName);
579 
580     srcClip->addSupportedComponent(ePixelComponentRGBA);
581     srcClip->addSupportedComponent(ePixelComponentRGB);
582 #ifdef OFX_EXTENSIONS_NATRON
583     srcClip->addSupportedComponent(ePixelComponentXY);
584 #endif
585     srcClip->addSupportedComponent(ePixelComponentAlpha);
586     srcClip->setTemporalClipAccess(true);
587     srcClip->setSupportsTiles(kSupportsTiles);
588     srcClip->setIsMask(false);
589 
590     // create the mandated output clip
591     ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
592     dstClip->addSupportedComponent(ePixelComponentRGBA);
593     dstClip->addSupportedComponent(ePixelComponentRGB);
594 #ifdef OFX_EXTENSIONS_NATRON
595     dstClip->addSupportedComponent(ePixelComponentXY);
596 #endif
597     dstClip->addSupportedComponent(ePixelComponentAlpha);
598     dstClip->setSupportsTiles(kSupportsTiles);
599 
600     // make some pages and to things in
601     PageParamDescriptor *page = desc.definePageParam("Controls");
602 
603     {
604         IntParamDescriptor *param = desc.defineIntParam(kParamDivisions);
605         param->setLabel(kParamDivisionsLabel);
606         param->setHint(kParamDivisionsHint);
607         param->setDefault(10);
608         param->setRange(1, INT_MAX);
609         param->setDisplayRange(1, 10);
610         param->setAnimates(true); // can animate
611         if (page) {
612             page->addChild(*param);
613         }
614     }
615     shutterDescribeInContext(desc, context, page);
616 }
617 
618 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)619 TimeBlurPluginFactory::createInstance(OfxImageEffectHandle handle,
620                                       ContextEnum /*context*/)
621 {
622     return new TimeBlurPlugin(handle);
623 }
624 
625 static TimeBlurPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
626 mRegisterPluginFactoryInstance(p)
627 
628 OFXS_NAMESPACE_ANONYMOUS_EXIT
629