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