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