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 Retime plugin.
21  * Change the timing of the input clip.
22  */
23 
24 /*
25    TODO: this plugin has to be improved a lot
26    - propose a "timewarp" curve (as ParametricParam)
27    - selection of the integration filter (box or nearest) and shutter time
28    - handle fielded input correctly
29 
30    - retiming based on optical flow computation will be done separately
31  */
32 
33 #include <cmath> // for floor
34 #include <cfloat> // DBL_MAX
35 #include <cassert>
36 
37 #include "ofxsImageEffect.h"
38 #include "ofxsThreadSuite.h"
39 #include "ofxsMultiThread.h"
40 
41 #include "ofxsProcessing.H"
42 #include "ofxsImageBlender.H"
43 #include "ofxsCopier.h"
44 #include "ofxsMacros.h"
45 
46 using namespace OFX;
47 
48 OFXS_NAMESPACE_ANONYMOUS_ENTER
49 
50 #define kPluginName "RetimeOFX"
51 #define kPluginGrouping "Time"
52 #define kPluginDescription "Change the timing of the input clip.\n" \
53     "See also: http://opticalenquiry.com/nuke/index.php?title=Retime"
54 
55 #define kPluginIdentifier "net.sf.openfx.Retime"
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 #define kParamReverseInput "reverseInput"
67 #define kParamReverseInputLabel "Reverse input"
68 #define kParamReverseInputHint "Reverse the order of the input frames so that last one is first"
69 
70 #define kParamSpeed "speed"
71 #define kParamSpeedLabel "Speed"
72 #define kParamSpeedHint "How much to change the speed of the input clip. To determine which input frame is taken at a given time, the speed is integrated from the beginning of the source frame range to the given time, so that speed can be animated to locally accelerate (speed > 1), decelerate (speed < 1) or reverse (speed < 0) the source clip. Note that this is is not the same as the speed parameter of the Nuke Retime node, which just multiplies the speed value at the current time by the time to obtain the source frame number."
73 
74 #define kParamDuration "duration"
75 #define kParamDurationLabel "Duration"
76 #define kParamDurationHint "How long the output clip should be, as a proportion of the input clip's length."
77 
78 #define kParamFilter "filter"
79 #define kParamFilterLabel "Filter"
80 #define kParamFilterHint "How input images are combined to compute the output image."
81 
82 #define kParamFilterOptionNone "None", "Do not interpolate, ask for images with fractional time to the input effect. Useful if the input effect can interpolate itself.", "none"
83 #define kParamFilterOptionNearest "Nearest", "Pick input image with nearest integer time.", "nearest"
84 #define kParamFilterOptionLinear "Linear", "Blend the two nearest images with linear interpolation.", "linear"
85 // TODO:
86 #define kParamFilterOptionBox "Box", "Weighted average of images over the shutter time (shutter time is defined in the output sequence).", "box" // requires shutter parameter
87 
88 enum FilterEnum
89 {
90     eFilterNone,
91     eFilterNearest,
92     eFilterLinear,
93     //eFilterBox,
94 };
95 
96 #define kParamFilterDefault eFilterLinear
97 
98 #define kPageTimeWarp "timeWarp"
99 #define kPageTimeWarpLabel "Time Warp"
100 
101 #define kParamWarp "warp"
102 #define kParamWarpLabel "Warp"
103 #define kParamWarpHint "Curve that maps input range (after applying speed) to the output range. A low positive slope slows down the input clip, and a negative slope plays it backwards."
104 
105 
106 ////////////////////////////////////////////////////////////////////////////////
107 /** @brief The plugin that does our work */
108 class RetimePlugin
109     : public ImageEffect
110 {
111 protected:
112     // do not need to delete these, the ImageEffect is managing them for us
113     Clip *_dstClip;            /**< @brief Mandated output clips */
114     Clip *_srcClip;            /**< @brief Mandated input clips */
115     BooleanParam  *_reverse_input;
116     DoubleParam  *_sourceTime; /**< @brief mandated parameter, only used in the retimer context. */
117     DoubleParam  *_speed;      /**< @brief only used in the filter or general context. */
118     ParametricParam  *_warp;      /**< @brief only used in the filter or general context. */
119     DoubleParam  *_duration;   /**< @brief how long the output should be as a proportion of input. General context only. */
120     ChoiceParam  *_filter;   /**< @brief how images are interpolated (or not). */
121 
122 public:
123     /** @brief ctor */
RetimePlugin(OfxImageEffectHandle handle,bool supportsParametricParameter)124     RetimePlugin(OfxImageEffectHandle handle,
125                  bool supportsParametricParameter)
126         : ImageEffect(handle)
127         , _dstClip(NULL)
128         , _srcClip(NULL)
129         , _reverse_input(NULL)
130         , _sourceTime(NULL)
131         , _speed(NULL)
132         , _warp(NULL)
133         , _duration(NULL)
134         , _filter(NULL)
135     {
136         _dstClip = fetchClip(kOfxImageEffectOutputClipName);
137         _srcClip = getContext() == eContextGenerator ? NULL : fetchClip(kOfxImageEffectSimpleSourceClipName);
138 
139         // What parameters we instantiate depend on the context
140         if (getContext() == eContextRetimer) {
141             // fetch the mandated parameter which the host uses to pass us the frame to retime to
142             _sourceTime = fetchDoubleParam(kOfxImageEffectRetimerParamName);
143             assert(_sourceTime);
144         } else { // context == eContextFilter || context == eContextGeneral
145             // filter context means we are in charge of how to retime, and our example is using a speed curve to do that
146             _reverse_input = fetchBooleanParam(kParamReverseInput);
147             _speed = fetchDoubleParam(kParamSpeed);
148             assert(_speed);
149             if (supportsParametricParameter) {
150                 _warp = fetchParametricParam(kParamWarp);
151                 assert(_warp);
152             } else if (getContext() == eContextGeneral) {
153                 // fetch duration param for general context
154                 _duration = fetchDoubleParam(kParamDuration);
155                 assert(_duration);
156             }
157         }
158         _filter = fetchChoiceParam(kParamFilter);
159         assert(_filter);
160     }
161 
162     /* Override the render */
163     virtual void render(const RenderArguments &args) OVERRIDE FINAL;
164 
165     template <int nComponents>
166     void renderInternal(const RenderArguments &args, double sourceTime, FilterEnum filter, BitDepthEnum dstBitDepth);
167 
168     /** Override the get frames needed action */
169     virtual void getFramesNeeded(const FramesNeededArguments &args, FramesNeededSetter &frames) OVERRIDE FINAL;
170     virtual bool isIdentity(const IsIdentityArguments &args, Clip * &identityClip, double &identityTime, int& view, std::string& plane) OVERRIDE FINAL;
171 
172     /* override the time domain action, only for the general context */
173     virtual bool getTimeDomain(OfxRangeD &range) OVERRIDE FINAL;
174     virtual bool getRegionOfDefinition(const RegionOfDefinitionArguments &args, OfxRectD &rod) OVERRIDE FINAL;
175 
176     /* set up and run a processor */
177     void setupAndProcess(ImageBlenderBase &, const RenderArguments &args, double sourceTime, FilterEnum filter);
178 
179 private:
180 
181 
182     bool isIdentityInternal(OfxTime time, Clip* &identityClip, OfxTime &identityTime);
183 };
184 
185 
186 ////////////////////////////////////////////////////////////////////////////////
187 /** @brief render for the filter */
188 
189 ////////////////////////////////////////////////////////////////////////////////
190 // basic plugin render function, just a skelington to instantiate templates from
191 
192 // make sure components are sane
193 static void
checkComponents(const Image & src,BitDepthEnum dstBitDepth,PixelComponentEnum dstComponents)194 checkComponents(const Image &src,
195                 BitDepthEnum dstBitDepth,
196                 PixelComponentEnum dstComponents)
197 {
198     BitDepthEnum srcBitDepth    = src.getPixelDepth();
199     PixelComponentEnum srcComponents  = src.getPixelComponents();
200 
201     // see if they have the same depths and bytes and all
202     if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
203         throwSuiteStatusException(kOfxStatErrImageFormat);
204     }
205 }
206 
207 static void
framesNeeded(double sourceTime,FieldEnum fieldToRender,double * fromTimep,double * toTimep,double * blendp)208 framesNeeded(double sourceTime,
209              FieldEnum fieldToRender,
210              double *fromTimep,
211              double *toTimep,
212              double *blendp)
213 {
214     // figure the two images we are blending between
215     double fromTime, toTime;
216     double blend;
217 
218     if (fieldToRender == eFieldNone) {
219         // unfielded, easy peasy
220         fromTime = std::floor(sourceTime);
221         toTime = fromTime + 1;
222         blend = sourceTime - fromTime;
223     } else {
224         // Fielded clips, pook. We are rendering field doubled images,
225         // and so need to blend between fields, not frames.
226         double frac = sourceTime - std::floor(sourceTime);
227         if (frac < 0.5) {
228             // need to go between the first and second fields of this frame
229             fromTime = std::floor(sourceTime); // this will get the first field
230             toTime   = fromTime + 0.5;    // this will get the second field of the same frame
231             blend    = frac * 2.0;        // and the blend is between those two
232         } else { // frac > 0.5
233             fromTime = std::floor(sourceTime) + 0.5; // this will get the second field of this frame
234             toTime   = std::floor(sourceTime) + 1.0; // this will get the first field of the next frame
235             blend    = (frac - 0.5) * 2.0;
236         }
237     }
238     *fromTimep = fromTime;
239     *toTimep = toTime;
240     *blendp = blend;
241 }
242 
243 /* set up and run a processor */
244 void
setupAndProcess(ImageBlenderBase & processor,const RenderArguments & args,double sourceTime,FilterEnum filter)245 RetimePlugin::setupAndProcess(ImageBlenderBase &processor,
246                               const RenderArguments &args,
247                               double sourceTime,
248                               FilterEnum filter)
249 {
250     const double time = args.time;
251 
252     // get a dst image
253     auto_ptr<Image>  dst( _dstClip->fetchImage(time) );
254 
255     if ( !dst.get() ) {
256         throwSuiteStatusException(kOfxStatFailed);
257     }
258     BitDepthEnum dstBitDepth    = dst->getPixelDepth();
259     PixelComponentEnum dstComponents  = dst->getPixelComponents();
260     if ( ( dstBitDepth != _dstClip->getPixelDepth() ) ||
261          ( dstComponents != _dstClip->getPixelComponents() ) ) {
262         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong depth or components");
263         throwSuiteStatusException(kOfxStatFailed);
264     }
265     if ( (dst->getRenderScale().x != args.renderScale.x) ||
266          ( dst->getRenderScale().y != args.renderScale.y) ||
267          ( ( dst->getField() != eFieldNone) /* for DaVinci Resolve */ && ( dst->getField() != args.fieldToRender) ) ) {
268         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
269         throwSuiteStatusException(kOfxStatFailed);
270     }
271 
272     if ( (sourceTime == (int)sourceTime) || (filter == eFilterNone) || (filter == eFilterNearest) ) {
273         // should have been caught by isIdentity...
274         auto_ptr<const Image> src( ( _srcClip && _srcClip->isConnected() ) ?
275                                         _srcClip->fetchImage(sourceTime) : 0 );
276         if ( src.get() ) {
277             if ( (src->getRenderScale().x != args.renderScale.x) ||
278                  ( src->getRenderScale().y != args.renderScale.y) ||
279                  ( ( src->getField() != eFieldNone) /* for DaVinci Resolve */ && ( src->getField() != args.fieldToRender) ) ) {
280                 setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
281                 throwSuiteStatusException(kOfxStatFailed);
282             }
283             BitDepthEnum srcBitDepth      = src->getPixelDepth();
284             PixelComponentEnum srcComponents = src->getPixelComponents();
285             if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
286                 throwSuiteStatusException(kOfxStatErrImageFormat);
287             }
288         }
289         copyPixels( *this, args.renderWindow, src.get(), dst.get() );
290 
291         return;
292     }
293 
294     // figure the two images we are blending between
295     double fromTime, toTime;
296     double blend;
297     framesNeeded(sourceTime, args.fieldToRender, &fromTime, &toTime, &blend);
298 
299     // fetch the two source images
300     auto_ptr<Image> fromImg( ( _srcClip && _srcClip->isConnected() ) ?
301                                   _srcClip->fetchImage(fromTime) : 0 );
302     auto_ptr<Image> toImg( ( _srcClip && _srcClip->isConnected() ) ?
303                                 _srcClip->fetchImage(toTime) : 0 );
304 
305     // make sure bit depths are sane
306     if ( fromImg.get() ) {
307         if ( (fromImg->getRenderScale().x != args.renderScale.x) ||
308              ( fromImg->getRenderScale().y != args.renderScale.y) ||
309              ( ( fromImg->getField() != eFieldNone) /* for DaVinci Resolve */ && ( fromImg->getField() != args.fieldToRender) ) ) {
310             setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
311             throwSuiteStatusException(kOfxStatFailed);
312         }
313         checkComponents(*fromImg, dstBitDepth, dstComponents);
314     }
315     if ( toImg.get() ) {
316         if ( (toImg->getRenderScale().x != args.renderScale.x) ||
317              ( toImg->getRenderScale().y != args.renderScale.y) ||
318              ( ( toImg->getField() != eFieldNone) /* for DaVinci Resolve */ && ( toImg->getField() != args.fieldToRender) ) ) {
319             setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
320             throwSuiteStatusException(kOfxStatFailed);
321         }
322         checkComponents(*toImg, dstBitDepth, dstComponents);
323     }
324 
325     // set the images
326     processor.setDstImg( dst.get() );
327     processor.setFromImg( fromImg.get() );
328     processor.setToImg( toImg.get() );
329 
330     // set the render window
331     processor.setRenderWindow(args.renderWindow);
332 
333     // set the blend between
334     processor.setBlend( (float)blend );
335 
336     // Call the base class process member, this will call the derived templated process code
337     processor.process();
338 } // RetimePlugin::setupAndProcess
339 
340 void
getFramesNeeded(const FramesNeededArguments & args,FramesNeededSetter & frames)341 RetimePlugin::getFramesNeeded(const FramesNeededArguments &args,
342                               FramesNeededSetter &frames)
343 {
344     if (!_srcClip || !_srcClip->isConnected()) {
345         return;
346     }
347     const double time = args.time;
348     double sourceTime;
349     if (getContext() == eContextRetimer) {
350         // the host is specifying it, so fetch it from the kOfxImageEffectRetimerParamName pseudo-param
351         sourceTime = _sourceTime->getValueAtTime(time);
352     } else {
353         bool reverse_input;
354         OfxRangeD srcRange = _srcClip->getFrameRange();
355         _reverse_input->getValueAtTime(time, reverse_input);
356         // we have our own param, which is a speed, so we integrate it to get the time we want
357         if (reverse_input) {
358             sourceTime = srcRange.max - _speed->integrate(srcRange.min, time);
359         } else {
360             sourceTime = srcRange.min + _speed->integrate(srcRange.min, time);
361         }
362         if (_warp) {
363             double r = srcRange.max - srcRange.min;
364             if (r != 0.) {
365                 sourceTime = srcRange.min + r * _warp->getValue(0, time, (sourceTime - srcRange.min) / r);
366             }
367         }
368     }
369 
370     FilterEnum filter = (FilterEnum)_filter->getValueAtTime(time);
371     OfxRangeD range;
372     if ( (sourceTime == (int)sourceTime) || (filter == eFilterNone) ) {
373         range.min = sourceTime;
374         range.max = sourceTime;
375     } else if (filter == eFilterNearest) {
376         range.min = range.max = std::floor(sourceTime + 0.5);
377     } else if (filter == eFilterLinear) {
378         // figure the two images we are blending between
379         double fromTime, toTime;
380         double blend;
381         // whatever the rendered field is, the frames are the same
382         framesNeeded(sourceTime, eFieldNone, &fromTime, &toTime, &blend);
383         range.min = fromTime;
384         range.max = toTime;
385     } else {
386         assert(false);
387     }
388     frames.setFramesNeeded(*_srcClip, range);
389 }
390 
391 bool
getRegionOfDefinition(const RegionOfDefinitionArguments & args,OfxRectD & rod)392 RetimePlugin::getRegionOfDefinition(const RegionOfDefinitionArguments &args,
393                                     OfxRectD &rod)
394 {
395     Clip* identityClip;
396     OfxTime identityTime;
397     bool identity = isIdentityInternal(args.time, identityClip, identityTime);
398 
399     if (!identity) {
400         return false;
401     }
402     rod = _srcClip->getRegionOfDefinition(identityTime, args.view);
403 
404     return true;
405 }
406 
407 bool
isIdentityInternal(OfxTime time,Clip * & identityClip,OfxTime & identityTime)408 RetimePlugin::isIdentityInternal(OfxTime time,
409                                  Clip* &identityClip,
410                                  OfxTime &identityTime)
411 {
412     if (!_srcClip || !_srcClip->isConnected()) {
413         return false;
414     }
415     double sourceTime;
416     if (getContext() == eContextRetimer) {
417         // the host is specifying it, so fetch it from the kOfxImageEffectRetimerParamName pseudo-param
418         sourceTime = _sourceTime->getValueAtTime(time);
419     } else {
420         bool reverse_input;
421         OfxRangeD srcRange = _srcClip->getFrameRange();
422         _reverse_input->getValueAtTime(time, reverse_input);
423         // we have our own param, which is a speed, so we integrate it to get the time we want
424         if (reverse_input) {
425             sourceTime = srcRange.max - _speed->integrate(srcRange.min, time);
426         } else {
427             sourceTime = srcRange.min + _speed->integrate(srcRange.min, time);
428         }
429         if (_warp) {
430             double r = srcRange.max - srcRange.min;
431             if (r != 0.) {
432                 sourceTime = srcRange.min + r * _warp->getValue(0, time, (sourceTime - srcRange.min) / r);
433             }
434         }
435     }
436     FilterEnum filter = (FilterEnum)_filter->getValueAtTime(time);
437 
438     if ( (sourceTime == (int)sourceTime) || (filter == eFilterNone) ) {
439         identityClip = _srcClip;
440         identityTime = sourceTime;
441 
442         return true;
443     }
444     if (filter == eFilterNearest) {
445         identityClip = _srcClip;
446         identityTime = std::floor(sourceTime + 0.5);
447 
448         return true;
449     }
450 
451     return false;
452 }
453 
454 bool
isIdentity(const IsIdentityArguments & args,Clip * & identityClip,double & identityTime,int &,std::string &)455 RetimePlugin::isIdentity(const IsIdentityArguments &args,
456                          Clip * &identityClip,
457                          double &identityTime
458                          , int& /*view*/, std::string& /*plane*/)
459 {
460     return isIdentityInternal(args.time, identityClip, identityTime);
461 }
462 
463 /* override the time domain action, only for the general context */
464 bool
getTimeDomain(OfxRangeD & range)465 RetimePlugin::getTimeDomain(OfxRangeD &range)
466 {
467     // this should only be called in the general context, ever!
468     if ( (getContext() == eContextGeneral) && _srcClip && _duration ) {
469         assert(!_warp);
470         // If we are a general context, we can changed the duration of the effect, so have a param to do that
471         // We need a separate param as it is impossible to derive this from a speed param and the input clip
472         // duration (the speed may be animating or wired to an expression).
473         double duration;
474         _duration->getValue(duration); //don't animate
475 
476         // how many frames on the input clip
477         OfxRangeD srcRange = _srcClip->getFrameRange();
478 
479         range.min = srcRange.min;
480         range.max = srcRange.min + (srcRange.max - srcRange.min) * duration;
481 
482         return true;
483     }
484 
485     // If there's a warp curve, the time domain could be determined from the intersections of the warp curve with y=0 and y=1.
486 
487     // for now, we prefer returning the input time domain.
488     return false;
489 }
490 
491 // the internal render function
492 template <int nComponents>
493 void
renderInternal(const RenderArguments & args,double sourceTime,FilterEnum filter,BitDepthEnum dstBitDepth)494 RetimePlugin::renderInternal(const RenderArguments &args,
495                              double sourceTime,
496                              FilterEnum filter,
497                              BitDepthEnum dstBitDepth)
498 {
499     switch (dstBitDepth) {
500     case eBitDepthUByte: {
501         ImageBlender<unsigned char, nComponents> fred(*this);
502         setupAndProcess(fred, args, sourceTime, filter);
503         break;
504     }
505     case eBitDepthUShort: {
506         ImageBlender<unsigned short, nComponents> fred(*this);
507         setupAndProcess(fred, args, sourceTime, filter);
508         break;
509     }
510     case eBitDepthFloat: {
511         ImageBlender<float, nComponents> fred(*this);
512         setupAndProcess(fred, args, sourceTime, filter);
513         break;
514     }
515     default:
516         throwSuiteStatusException(kOfxStatErrUnsupported);
517     }
518 }
519 
520 // the overridden render function
521 void
render(const RenderArguments & args)522 RetimePlugin::render(const RenderArguments &args)
523 {
524     const double time = args.time;
525     // instantiate the render code based on the pixel depth of the dst clip
526     BitDepthEnum dstBitDepth    = _dstClip->getPixelDepth();
527     PixelComponentEnum dstComponents  = _dstClip->getPixelComponents();
528 
529     assert( kSupportsMultipleClipPARs   || !_srcClip || _srcClip->getPixelAspectRatio() == _dstClip->getPixelAspectRatio() );
530     assert( kSupportsMultipleClipDepths || !_srcClip || _srcClip->getPixelDepth()       == _dstClip->getPixelDepth() );
531 
532     // figure the frame we should be retiming from
533     double sourceTime = time;
534 
535     if (getContext() == eContextRetimer) {
536         // the host is specifying it, so fetch it from the kOfxImageEffectRetimerParamName pseudo-param
537         sourceTime = _sourceTime->getValueAtTime(time);
538     } else if (_srcClip) {
539         bool reverse_input;
540         OfxRangeD srcRange = _srcClip->getFrameRange();
541         _reverse_input->getValueAtTime(time, reverse_input);
542         // we have our own param, which is a speed, so we integrate it to get the time we want
543         if (reverse_input) {
544             sourceTime = srcRange.max - _speed->integrate(srcRange.min, time);
545         } else {
546             sourceTime = srcRange.min + _speed->integrate(srcRange.min, time);
547         }
548         if (_warp) {
549             double r = srcRange.max - srcRange.min;
550             if (r != 0.) {
551                 sourceTime = srcRange.min + r * _warp->getValue(0, time, (sourceTime - srcRange.min) / r);
552             }
553         }
554     }
555 
556     FilterEnum filter = (FilterEnum)_filter->getValueAtTime(time);
557 
558 #ifdef DEBUG
559     if ( (sourceTime == (int)sourceTime) || (filter == eFilterNone) || (filter == eFilterNearest) ) {
560         // should be caught by isIdentity!
561         setPersistentMessage(Message::eMessageError, "", "OFX Host should not render");
562         throwSuiteStatusException(kOfxStatFailed);
563     }
564 #endif
565 
566     // do the rendering
567     if (dstComponents == ePixelComponentRGBA) {
568         renderInternal<4>(args, sourceTime, filter, dstBitDepth);
569     } else if (dstComponents == ePixelComponentRGB) {
570         renderInternal<3>(args, sourceTime, filter, dstBitDepth);
571 #ifdef OFX_EXTENSIONS_NATRON
572     } else if (dstComponents == ePixelComponentXY) {
573         renderInternal<2>(args, sourceTime, filter, dstBitDepth);
574 #endif
575     } else {
576         assert(dstComponents == ePixelComponentAlpha);
577         renderInternal<1>(args, sourceTime, filter, dstBitDepth);
578     }
579 } // RetimePlugin::render
580 
581 mDeclarePluginFactory(RetimePluginFactory,; , {});
582 void
load()583 RetimePluginFactory::load()
584 {
585     ofxsThreadSuiteCheck();
586     // we can't be used on hosts that don't perfrom temporal clip access
587     if (!getImageEffectHostDescription()->temporalClipAccess) {
588         throw Exception::HostInadequate("Need random temporal image access to work");
589     }
590 }
591 
592 /** @brief The basic describe function, passed a plugin descriptor */
593 void
describe(ImageEffectDescriptor & desc)594 RetimePluginFactory::describe(ImageEffectDescriptor &desc)
595 {
596     // basic labels
597     desc.setLabel(kPluginName);
598     desc.setPluginGrouping(kPluginGrouping);
599     desc.setPluginDescription(kPluginDescription);
600 
601     // Say we are a transition context
602     desc.addSupportedContext(eContextRetimer);
603     desc.addSupportedContext(eContextFilter);
604     desc.addSupportedContext(eContextGeneral);
605 
606     // Add supported pixel depths
607     desc.addSupportedBitDepth(eBitDepthUByte);
608     desc.addSupportedBitDepth(eBitDepthUShort);
609     desc.addSupportedBitDepth(eBitDepthFloat);
610 
611     // set a few flags
612     desc.setSingleInstance(false);
613     desc.setHostFrameThreading(false);
614     desc.setSupportsMultiResolution(kSupportsMultiResolution);
615     desc.setSupportsTiles(kSupportsTiles);
616     desc.setTemporalClipAccess(true); // say we will be doing random time access on clips
617     desc.setRenderTwiceAlways(true); // each field has to be rendered separately, since it may come from a different time
618     desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
619     desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);
620     desc.setRenderThreadSafety(kRenderThreadSafety);
621 
622     // we can't be used on hosts that don't perfrom temporal clip access
623     if (!getImageEffectHostDescription()->temporalClipAccess) {
624         throw Exception::HostInadequate("Need random temporal image access to work");
625     }
626 #ifdef OFX_EXTENSIONS_NATRON
627     desc.setChannelSelector(ePixelComponentNone);
628 #endif
629 }
630 
631 /** @brief The describe in context function, passed a plugin descriptor and a context */
632 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)633 RetimePluginFactory::describeInContext(ImageEffectDescriptor &desc,
634                                        ContextEnum context)
635 {
636     // we are a transition, so define the sourceTo input clip
637     ClipDescriptor *srcClip = desc.defineClip(kOfxImageEffectSimpleSourceClipName);
638 
639     srcClip->addSupportedComponent(ePixelComponentRGBA);
640     srcClip->addSupportedComponent(ePixelComponentRGB);
641 #ifdef OFX_EXTENSIONS_NATRON
642     srcClip->addSupportedComponent(ePixelComponentXY);
643 #endif
644     srcClip->addSupportedComponent(ePixelComponentAlpha);
645     srcClip->setTemporalClipAccess(true); // say we will be doing random time access on this clip
646     srcClip->setSupportsTiles(kSupportsTiles);
647     srcClip->setFieldExtraction(eFieldExtractDoubled); // which is the default anyway
648 
649     // create the mandated output clip
650     ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
651     dstClip->addSupportedComponent(ePixelComponentRGBA);
652     dstClip->addSupportedComponent(ePixelComponentRGB);
653 #ifdef OFX_EXTENSIONS_NATRON
654     dstClip->addSupportedComponent(ePixelComponentXY);
655 #endif
656     dstClip->addSupportedComponent(ePixelComponentAlpha);
657     dstClip->setFieldExtraction(eFieldExtractDoubled); // which is the default anyway
658     dstClip->setSupportsTiles(kSupportsTiles);
659 
660     // make a page to put it in
661     PageParamDescriptor *page = desc.definePageParam("Controls");
662 
663     // what param we have is dependant on the host
664     if (context == eContextRetimer) {
665         // Define the mandated kOfxImageEffectRetimerParamName param, note that we don't do anything with this other than.
666         // describe it. It is not a true param but how the host indicates to the plug-in which frame
667         // it wants you to retime to. It appears on no plug-in side UI, it is purely the host's to manage.
668         DoubleParamDescriptor *param = desc.defineDoubleParam(kOfxImageEffectRetimerParamName);
669         (void)param;
670     }  else {
671         // We are a general or filter context, define a speed param and a page of controls to put that in
672         // reverse_input
673         {
674             BooleanParamDescriptor *param = desc.defineBooleanParam(kParamReverseInput);
675             param->setDefault(false);
676             param->setHint(kParamReverseInputHint);
677             param->setLabel(kParamReverseInputLabel);
678             param->setAnimates(true);
679             if (page) {
680                 page->addChild(*param);
681             }
682         }
683 
684         {
685             DoubleParamDescriptor *param = desc.defineDoubleParam(kParamSpeed);
686             param->setLabel(kParamSpeedLabel);
687             param->setHint(kParamSpeedHint);
688             param->setDefault(1);
689             param->setIncrement(0.05);
690             param->setRange(-DBL_MAX, DBL_MAX);
691             param->setDisplayRange(0.1, 10.);
692             param->setAnimates(true); // can animate
693             param->setCacheInvalidation(eCacheInvalidateValueChangeToEnd); // any speed change affects all frames to end of sequence
694             param->setDoubleType(eDoubleTypeScale);
695             if (page) {
696                 page->addChild(*param);
697             }
698         }
699 
700         const ImageEffectHostDescription &gHostDescription = *getImageEffectHostDescription();
701         const bool supportsParametricParameter = ( gHostDescription.supportsParametricParameter &&
702                                                    !(gHostDescription.hostName == "uk.co.thefoundry.nuke" &&
703                                                      8 <= gHostDescription.versionMajor && gHostDescription.versionMajor <= 10) ); // Nuke 8-10 are known to *not* support Parametric
704         if (supportsParametricParameter) {
705             PageParamDescriptor* page = desc.definePageParam(kPageTimeWarp);
706             if (page) {
707                 page->setLabel(kPageTimeWarpLabel);
708             }
709             {
710                 ParametricParamDescriptor* param = desc.defineParametricParam(kParamWarp);
711                 assert(param);
712                 param->setLabel(kParamWarpLabel);
713                 param->setHint(kParamWarpHint);
714 
715                 // define it as one dimensional
716                 param->setDimension(1);
717                 param->setDimensionLabel(kParamWarp, 0);
718 
719                 const OfxRGBColourD blue  = {0.5, 0.5, 1};      //set blue color to blue curve
720                 param->setUIColour( 0, blue );
721 
722                 // set the min/max parametric range to 0..1
723                 param->setRange(0.0, 1.0);
724                 // set the default Y range to 0..1 for all dimensions
725                 param->setDimensionDisplayRange(0., 1., 0);
726 
727                 param->setIdentity(0);
728 
729                 // add param to page
730                 if (page) {
731                     page->addChild(*param);
732                 }
733             }
734         } else if (context == eContextGeneral) {
735             // If we are a general context, we can change the duration of the effect, so have a param to do that
736             // We need a separate param as it is impossible to derive this from a speed param and the input clip
737             // duration (the speed may be animating or wired to an expression).
738 
739             // This is not possible if there's a warp curve.
740 
741             // We are a general or filter context, define a speed param and a page of controls to put that in
742             DoubleParamDescriptor *param = desc.defineDoubleParam(kParamDuration);
743             param->setLabel(kParamDurationLabel);
744             param->setHint(kParamDurationHint);
745             param->setDefault(1);
746             param->setIncrement(0.1);
747             param->setRange(0, 10);
748             param->setDisplayRange(0, 10);
749             param->setAnimates(false); // used in getTimeDomain()
750             param->setDoubleType(eDoubleTypeScale);
751 
752             // add param to page
753             if (page) {
754                 page->addChild(*param);
755             }
756         }
757     }
758 
759     {
760         ChoiceParamDescriptor *param = desc.defineChoiceParam(kParamFilter);
761         param->setLabel(kParamFilterLabel);
762         param->setHint(kParamFilterHint);
763         assert(param->getNOptions() == eFilterNone);
764         param->appendOption(kParamFilterOptionNone);
765         assert(param->getNOptions() == eFilterNearest);
766         param->appendOption(kParamFilterOptionNearest);
767         assert(param->getNOptions() == eFilterLinear);
768         param->appendOption(kParamFilterOptionLinear);
769         //assert(param->getNOptions() == eFilterBox);
770         //param->appendOption(kParamFilterOptionBox, kParamFilterOptionBoxHint);
771         param->setDefault( (int)kParamFilterDefault );
772         if (page) {
773             page->addChild(*param);
774         }
775     }
776 } // RetimePluginFactory::describeInContext
777 
778 /** @brief The create instance function, the plugin must return an object derived from the \ref ImageEffect class */
779 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)780 RetimePluginFactory::createInstance(OfxImageEffectHandle handle,
781                                     ContextEnum /*context*/)
782 {
783     const ImageEffectHostDescription &gHostDescription = *getImageEffectHostDescription();
784     const bool supportsParametricParameter = ( gHostDescription.supportsParametricParameter &&
785                                                !(gHostDescription.hostName == "uk.co.thefoundry.nuke" &&
786                                                  8 <= gHostDescription.versionMajor && gHostDescription.versionMajor <= 10) );  // Nuke 8-10 are known to *not* support Parametric
787 
788     return new RetimePlugin(handle, supportsParametricParameter);
789 }
790 
791 static RetimePluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
792 mRegisterPluginFactoryInstance(p)
793 
794 OFXS_NAMESPACE_ANONYMOUS_EXIT
795