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 FrameRange plugin.
21  */
22 
23 
24 #include <algorithm> // for std::max
25 #include <cmath> // for std::floor, std::ceil
26 #include <stdio.h> // for snprintf & _snprintf
27 #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
28 #  define NOMINMAX
29 #  include <windows.h>
30 #  if defined(_MSC_VER) && _MSC_VER < 1900
31 #    define snprintf _snprintf
32 #  endif
33 #endif // defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
34 
35 #include "ofxsProcessing.H"
36 #include "ofxsMacros.h"
37 #include "ofxsCopier.h"
38 #ifdef OFX_EXTENSIONS_NATRON
39 #include "ofxNatron.h"
40 #endif
41 
42 #ifdef OFX_EXTENSIONS_NUKE
43 #include "nuke/fnOfxExtensions.h"
44 #endif
45 
46 using namespace OFX;
47 
48 OFXS_NAMESPACE_ANONYMOUS_ENTER
49 
50 #define kPluginName "FrameRangeOFX"
51 #define kPluginGrouping "Time"
52 #define kPluginDescription "Set the frame range for a clip. Useful in conjunction with AppendClipOFX."
53 #define kPluginIdentifier "net.sf.openfx.FrameRange"
54 #define kPluginVersionMajor 1 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
55 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
56 
57 #define kSupportsTiles 1
58 #define kSupportsMultiResolution 1
59 #define kSupportsRenderScale 1
60 #define kSupportsMultipleClipPARs false
61 #define kSupportsMultipleClipDepths true
62 #define kRenderThreadSafety eRenderFullySafe
63 
64 #define kParamFrameRange "frameRange"
65 #define kParamFrameRangeLabel "Frame Range"
66 #define kParamFrameRangeHint "Output frame range."
67 
68 #define kParamReset "reset"
69 #define kParamResetLabel "Reset"
70 #define kParamResetHint "Resets the frame range to its initial value."
71 
72 #define kParamBefore "before"
73 #define kParamBeforeLabel "Before"
74 #define kParamBeforeHint "What the plugin should return for frames before the first frame."
75 #define kParamAfter "after"
76 #define kParamAfterLabel "After"
77 #define kParamAfterHint "What the plugin should return for frames after the last frame."
78 #define kParamBeforeAfterOptionOriginal "Original", "Return the original frame from the source, even if it is out of the frame range.", "original"
79 #define kParamBeforeAfterOptionHold "Hold", "Return the nearest frame within the frame range.", "hold"
80 #define kParamBeforeAfterOptionBlack "Black", "Return an empty frame.", "black"
81 
82 enum BeforeAfterEnum
83 {
84     eBeforeAfterOriginal = 0,
85     eBeforeAfterHold,
86     eBeforeAfterBlack,
87 };
88 
89 
90 ////////////////////////////////////////////////////////////////////////////////
91 /** @brief The plugin that does our work */
92 class FrameRangePlugin
93     : public ImageEffect
94 {
95 public:
96     /** @brief ctor */
FrameRangePlugin(OfxImageEffectHandle handle)97     FrameRangePlugin(OfxImageEffectHandle handle)
98         : ImageEffect(handle)
99         , _dstClip(NULL)
100         , _srcClip(NULL)
101         , _frameRange(NULL)
102         , _before(NULL)
103         , _after(NULL)
104         , _sublabel(NULL)
105     {
106         _dstClip = fetchClip(kOfxImageEffectOutputClipName);
107         _srcClip = getContext() == eContextGenerator ? NULL : fetchClip(kOfxImageEffectSimpleSourceClipName);
108         _frameRange = fetchInt2DParam(kParamFrameRange);
109         _before = fetchChoiceParam(kParamBefore);
110         _after = fetchChoiceParam(kParamAfter);
111         assert(_frameRange && _before && _after);
112         _sublabel = fetchStringParam(kNatronOfxParamStringSublabelName);
113         assert(_sublabel);
114 
115         OfxPointI range = _frameRange->getValue();
116         refreshSubLabel(range.x, range.y);
117     }
118 
119 private:
120     /* Override the render */
121     virtual void render(const RenderArguments &args) OVERRIDE FINAL;
122     virtual bool isIdentity(const IsIdentityArguments &args, Clip * &identityClip, double &identityTime, int& view, std::string& plane) OVERRIDE FINAL;
123     virtual bool getRegionOfDefinition(const RegionOfDefinitionArguments &args, OfxRectD &rod) OVERRIDE FINAL;
124 
125 #ifdef OFX_EXTENSIONS_NUKE
126     /** @brief recover a transform matrix from an effect */
127     virtual bool getTransform(const TransformArguments & args, Clip * &transformClip, double transformMatrix[9]) OVERRIDE FINAL;
128 #endif
129 
130     /** @brief called when a clip has just been changed in some way (a rewire maybe) */
131     virtual void changedClip(const InstanceChangedArgs &args, const std::string &clipName) OVERRIDE FINAL;
132     virtual void changedParam(const InstanceChangedArgs &args, const std::string &paramName) OVERRIDE FINAL;
133 
134     /* override the time domain action, only for the general context */
135     virtual bool getTimeDomain(OfxRangeD &range) OVERRIDE FINAL;
136     virtual void getClipPreferences(ClipPreferencesSetter &clipPreferences) OVERRIDE FINAL;
137 
138 private:
139 
140     void refreshSubLabel(int rangeMin, int rangeMax);
141     // do not need to delete these, the ImageEffect is managing them for us
142     Clip *_dstClip;
143     Clip *_srcClip;
144     Int2DParam *_frameRange;
145     ChoiceParam *_before;
146     ChoiceParam *_after;
147     StringParam *_sublabel;
148 };
149 
150 
151 void
getClipPreferences(ClipPreferencesSetter & clipPreferences)152 FrameRangePlugin::getClipPreferences(ClipPreferencesSetter &clipPreferences)
153 {
154     // setting an image to black outside of the frame range means that the effect is frame varying
155     BeforeAfterEnum before = (BeforeAfterEnum)_before->getValue();
156 
157     if (before == eBeforeAfterBlack) {
158         clipPreferences.setOutputFrameVarying(true);
159     } else {
160         BeforeAfterEnum after = (BeforeAfterEnum)_after->getValue();
161         if (after == eBeforeAfterBlack) {
162             clipPreferences.setOutputFrameVarying(true);
163         }
164     }
165 }
166 
167 ////////////////////////////////////////////////////////////////////////////////
168 /** @brief render for the filter */
169 
170 ////////////////////////////////////////////////////////////////////////////////
171 // basic plugin render function, just a skelington to instantiate templates from
172 
173 
174 // the overridden render function
175 void
render(const RenderArguments & args)176 FrameRangePlugin::render(const RenderArguments &args)
177 {
178     const double time = args.time;
179     OfxPointI range = _frameRange->getValue();
180     double srcTime = time;
181     bool black = false;
182 
183     if (time < range.x) {
184         BeforeAfterEnum before = (BeforeAfterEnum)_before->getValue();
185         if (before == eBeforeAfterBlack) {
186             black = true;
187         } else if (before == eBeforeAfterHold) {
188             srcTime = range.x;
189         }
190     } else if (time > range.y) {
191         BeforeAfterEnum after = (BeforeAfterEnum)_after->getValue();
192         if (after == eBeforeAfterBlack) {
193             black = true;
194         } else if (after == eBeforeAfterHold) {
195             srcTime = range.y;
196         }
197     }
198 
199     assert( kSupportsMultipleClipPARs   || !_srcClip || _srcClip->getPixelAspectRatio() == _dstClip->getPixelAspectRatio() );
200     assert( kSupportsMultipleClipDepths || !_srcClip || _srcClip->getPixelDepth()       == _dstClip->getPixelDepth() );
201     // do the rendering
202     auto_ptr<Image> dst( _dstClip->fetchImage(args.time) );
203     if ( !dst.get() ) {
204         throwSuiteStatusException(kOfxStatFailed);
205     }
206     if ( (dst->getRenderScale().x != args.renderScale.x) ||
207          ( dst->getRenderScale().y != args.renderScale.y) ||
208          ( ( dst->getField() != eFieldNone) /* for DaVinci Resolve */ && ( dst->getField() != args.fieldToRender) ) ) {
209         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
210         throwSuiteStatusException(kOfxStatFailed);
211     }
212     BitDepthEnum dstBitDepth       = dst->getPixelDepth();
213     PixelComponentEnum dstComponents  = dst->getPixelComponents();
214     auto_ptr<const Image> src( (_srcClip && _srcClip->isConnected() && !black) ?
215                                     _srcClip->fetchImage(srcTime) : 0 );
216     if ( src.get() ) {
217         if ( (src->getRenderScale().x != args.renderScale.x) ||
218              ( src->getRenderScale().y != args.renderScale.y) ||
219              ( ( src->getField() != eFieldNone) /* for DaVinci Resolve */ && ( src->getField() != args.fieldToRender) ) ) {
220             setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
221             throwSuiteStatusException(kOfxStatFailed);
222         }
223         BitDepthEnum srcBitDepth      = src->getPixelDepth();
224         PixelComponentEnum srcComponents = src->getPixelComponents();
225         if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
226             throwSuiteStatusException(kOfxStatErrImageFormat);
227         }
228     }
229     if (black) {
230         fillBlack( *this, args.renderWindow, dst.get() );
231     } else {
232         copyPixels( *this, args.renderWindow, src.get(), dst.get() );
233     }
234 } // FrameRangePlugin::render
235 
236 bool
isIdentity(const IsIdentityArguments & args,Clip * & identityClip,double & identityTime,int &,std::string &)237 FrameRangePlugin::isIdentity(const IsIdentityArguments &args,
238                              Clip * &identityClip,
239                              double &identityTime
240                              , int& /*view*/, std::string& /*plane*/)
241 {
242     const double time = args.time;
243     OfxPointI range = _frameRange->getValue();
244 
245     if (time < range.x) {
246         BeforeAfterEnum before = (BeforeAfterEnum)_before->getValue();
247         if (before == eBeforeAfterBlack) {
248             return false;
249         } else if (before == eBeforeAfterHold) {
250             identityTime = range.x;
251         }
252     } else if (time > range.y) {
253         BeforeAfterEnum after = (BeforeAfterEnum)_after->getValue();
254         if (after == eBeforeAfterBlack) {
255             return false;
256         } else if (after == eBeforeAfterHold) {
257             identityTime = range.y;
258         }
259     }
260     identityClip = _srcClip;
261 
262     return true;
263 }
264 
265 bool
getRegionOfDefinition(const RegionOfDefinitionArguments & args,OfxRectD & rod)266 FrameRangePlugin::getRegionOfDefinition(const RegionOfDefinitionArguments &args,
267                                         OfxRectD &rod)
268 {
269     if ( !_srcClip || !_srcClip->isConnected() ) {
270         return false;
271     }
272     const double time = args.time;
273     OfxPointI range = _frameRange->getValue();
274     if (time < range.x) {
275         BeforeAfterEnum before = (BeforeAfterEnum)_before->getValue();
276         if (before == eBeforeAfterBlack) {
277             rod.x1 = rod.y1 = rod.x2 = rod.y2 = 0.;
278 
279             return true;
280         } else if (before == eBeforeAfterHold) {
281             rod = _srcClip->getRegionOfDefinition(range.x);
282 
283             return true;
284         }
285     } else if (time > range.y) {
286         BeforeAfterEnum after = (BeforeAfterEnum)_after->getValue();
287         if (after == eBeforeAfterBlack) {
288             rod.x1 = rod.y1 = rod.x2 = rod.y2 = 0.;
289 
290             return true;
291         } else if (after == eBeforeAfterHold) {
292             rod = _srcClip->getRegionOfDefinition(range.y);
293 
294             return true;
295         }
296     }
297 
298     return false;
299 }
300 
301 #ifdef OFX_EXTENSIONS_NUKE
302 // overridden getTransform
303 bool
getTransform(const TransformArguments & args,Clip * & transformClip,double transformMatrix[9])304 FrameRangePlugin::getTransform(const TransformArguments &args,
305                                Clip * &transformClip,
306                                double transformMatrix[9])
307 {
308     const double time = args.time;
309     OfxPointI range = _frameRange->getValue();
310 
311     if (time < range.x) {
312         BeforeAfterEnum before = (BeforeAfterEnum)_before->getValue();
313         if (before != eBeforeAfterOriginal) {
314             return false;
315         }
316     } else if (time > range.y) {
317         BeforeAfterEnum after = (BeforeAfterEnum)_after->getValue();
318         if (after != eBeforeAfterOriginal) {
319             return false;
320         }
321     }
322     transformClip = _srcClip;
323     transformMatrix[0] = 1.;
324     transformMatrix[1] = 0.;
325     transformMatrix[2] = 0.;
326     transformMatrix[3] = 0.;
327     transformMatrix[4] = 1.;
328     transformMatrix[5] = 0.;
329     transformMatrix[6] = 0.;
330     transformMatrix[7] = 0.;
331     transformMatrix[8] = 1.;
332 
333     return true;
334 }
335 
336 #endif
337 
338 
339 /** @brief called when a clip has just been changed in some way (a rewire maybe) */
340 void
changedClip(const InstanceChangedArgs & args,const std::string & clipName)341 FrameRangePlugin::changedClip(const InstanceChangedArgs &args,
342                               const std::string &clipName)
343 {
344     if ( (clipName == kOfxImageEffectSimpleSourceClipName) &&
345          ( args.reason == eChangeUserEdit) &&
346          _srcClip &&
347          _srcClip->isConnected() ) {
348         // if range is (1,1), i.e. the default value, set it to the input range
349         int min, max;
350         _frameRange->getValue(min, max);
351         if ( (min == 1) && (max == 1) ) {
352             OfxRangeD srcRange = _srcClip->getFrameRange();
353             _frameRange->setValue( std::floor(srcRange.min), std::ceil(srcRange.max) );
354         }
355     }
356 }
357 
358 void
refreshSubLabel(int rangeMin,int rangeMax)359 FrameRangePlugin::refreshSubLabel(int rangeMin, int rangeMax)
360 {
361     char label[80];
362     snprintf(label, sizeof(label), "%d - %d", rangeMin, rangeMax);
363     _sublabel->setValue(label);
364 }
365 
366 void
changedParam(const InstanceChangedArgs & args,const std::string & paramName)367 FrameRangePlugin::changedParam(const InstanceChangedArgs &args,
368                                const std::string &paramName)
369 {
370     if ( (paramName == kParamReset) && _srcClip && _srcClip->isConnected() && (args.reason == eChangeUserEdit) ) {
371         OfxRangeD range = _srcClip->getFrameRange();
372         _frameRange->setValue( (int)std::floor(range.min), (int)std::ceil(range.max) );
373         refreshSubLabel((int)std::floor(range.min), (int)std::ceil(range.max));
374     }
375     if ( (paramName == kParamFrameRange) && (args.reason == eChangeUserEdit) ) {
376 
377         OfxPointI range = _frameRange->getValue();
378         refreshSubLabel(range.x, range.y);
379     }
380 }
381 
382 /* override the time domain action, only for the general context */
383 bool
getTimeDomain(OfxRangeD & range)384 FrameRangePlugin::getTimeDomain(OfxRangeD &range)
385 {
386     // this should only be called in the general context, ever!
387     assert (getContext() == eContextGeneral);
388     int min, max;
389     _frameRange->getValue(min, max);
390     range.min = min;
391     range.max = std::max(min, max);
392 
393     return true;
394 }
395 
396 mDeclarePluginFactory(FrameRangePluginFactory, {ofxsThreadSuiteCheck();}, {});
397 void
describe(ImageEffectDescriptor & desc)398 FrameRangePluginFactory::describe(ImageEffectDescriptor &desc)
399 {
400     // basic labels
401     desc.setLabel(kPluginName);
402     desc.setPluginGrouping(kPluginGrouping);
403     desc.setPluginDescription(kPluginDescription);
404 
405     // add the supported contexts, only general, because the only useful action is getTimeDomain
406     desc.addSupportedContext(eContextGeneral);
407 
408     // add supported pixel depths
409     desc.addSupportedBitDepth(eBitDepthNone);
410     desc.addSupportedBitDepth(eBitDepthUByte);
411     desc.addSupportedBitDepth(eBitDepthUShort);
412     desc.addSupportedBitDepth(eBitDepthHalf);
413     desc.addSupportedBitDepth(eBitDepthFloat);
414     desc.addSupportedBitDepth(eBitDepthCustom);
415 #ifdef OFX_EXTENSIONS_VEGAS
416     desc.addSupportedBitDepth(eBitDepthUByteBGRA);
417     desc.addSupportedBitDepth(eBitDepthUShortBGRA);
418     desc.addSupportedBitDepth(eBitDepthFloatBGRA);
419 #endif
420 
421     // set a few flags
422     desc.setSingleInstance(false);
423     desc.setHostFrameThreading(false);
424     desc.setSupportsMultiResolution(kSupportsMultiResolution);
425     desc.setSupportsTiles(kSupportsTiles);
426     desc.setTemporalClipAccess(false);
427     desc.setRenderTwiceAlways(false);
428     desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
429     desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);
430     desc.setRenderThreadSafety(kRenderThreadSafety);
431 #ifdef OFX_EXTENSIONS_NUKE
432     // Enable transform by the host.
433     // It is only possible for transforms which can be represented as a 3x3 matrix.
434     desc.setCanTransform(true);
435     // ask the host to render all planes
436     desc.setPassThroughForNotProcessedPlanes(ePassThroughLevelRenderAllRequestedPlanes);
437 #endif
438 #ifdef OFX_EXTENSIONS_NATRON
439     desc.setChannelSelector(ePixelComponentNone);
440 #endif
441 }
442 
443 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum)444 FrameRangePluginFactory::describeInContext(ImageEffectDescriptor &desc,
445                                            ContextEnum /*context*/)
446 {
447     // Source clip only in the filter context
448     // create the mandated source clip
449     ClipDescriptor *srcClip = desc.defineClip(kOfxImageEffectSimpleSourceClipName);
450 
451     srcClip->addSupportedComponent(ePixelComponentNone);
452     srcClip->addSupportedComponent(ePixelComponentRGBA);
453     srcClip->addSupportedComponent(ePixelComponentRGB);
454     srcClip->addSupportedComponent(ePixelComponentAlpha);
455 #ifdef OFX_EXTENSIONS_NATRON
456     srcClip->addSupportedComponent(ePixelComponentXY);
457 #endif
458 #ifdef OFX_EXTENSIONS_NUKE
459     srcClip->setCanTransform(true);
460 #endif
461     srcClip->setTemporalClipAccess(false);
462     srcClip->setSupportsTiles(kSupportsTiles);
463     srcClip->setIsMask(false);
464 
465     // create the mandated output clip
466     ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
467     dstClip->addSupportedComponent(ePixelComponentNone);
468     dstClip->addSupportedComponent(ePixelComponentRGBA);
469     dstClip->addSupportedComponent(ePixelComponentRGB);
470     dstClip->addSupportedComponent(ePixelComponentAlpha);
471 #ifdef OFX_EXTENSIONS_NATRON
472     dstClip->addSupportedComponent(ePixelComponentXY);
473 #endif
474     dstClip->setSupportsTiles(kSupportsTiles);
475 
476     // make some pages and to things in
477     PageParamDescriptor *page = desc.definePageParam("Controls");
478 
479     // frameRange
480     {
481         Int2DParamDescriptor *param = desc.defineInt2DParam(kParamFrameRange);
482         param->setLabel(kParamFrameRangeLabel);
483         param->setHint(kParamFrameRangeHint);
484         param->setDefault(1, 1);
485         param->setDimensionLabels("first", "last");
486         param->setLayoutHint(eLayoutHintNoNewLine, 1);
487         param->setAnimates(false); // used in getTimeDomain()
488         if (page) {
489             page->addChild(*param);
490         }
491     }
492     // reset
493     {
494         PushButtonParamDescriptor *param = desc.definePushButtonParam(kParamReset);
495         param->setLabel(kParamResetLabel);
496         param->setHint(kParamResetHint);
497         if (page) {
498             page->addChild(*param);
499         }
500     }
501     // before
502     {
503         ChoiceParamDescriptor *param = desc.defineChoiceParam(kParamBefore);
504         param->setLabel(kParamBeforeLabel);
505         param->setHint(kParamBeforeHint);
506         assert(param->getNOptions() == (int)eBeforeAfterOriginal);
507         param->appendOption(kParamBeforeAfterOptionOriginal);
508         assert(param->getNOptions() == (int)eBeforeAfterHold);
509         param->appendOption(kParamBeforeAfterOptionHold);
510         assert(param->getNOptions() == (int)eBeforeAfterBlack);
511         param->appendOption(kParamBeforeAfterOptionBlack);
512         param->setDefault( (int)eBeforeAfterBlack );
513         param->setAnimates(false);
514         if (page) {
515             page->addChild(*param);
516         }
517     }
518     // after
519     {
520         ChoiceParamDescriptor *param = desc.defineChoiceParam(kParamAfter);
521         param->setLabel(kParamAfterLabel);
522         param->setHint(kParamAfterHint);
523         assert(param->getNOptions() == (int)eBeforeAfterOriginal);
524         param->appendOption(kParamBeforeAfterOptionOriginal);
525         assert(param->getNOptions() == (int)eBeforeAfterHold);
526         param->appendOption(kParamBeforeAfterOptionHold);
527         assert(param->getNOptions() == (int)eBeforeAfterBlack);
528         param->appendOption(kParamBeforeAfterOptionBlack);
529         param->setDefault( (int)eBeforeAfterBlack );
530         param->setAnimates(false);
531         if (page) {
532             page->addChild(*param);
533         }
534     }
535     // sublabel
536     {
537         StringParamDescriptor* param = desc.defineStringParam(kNatronOfxParamStringSublabelName);
538         param->setIsSecretAndDisabled(true); // always secret
539         param->setIsPersistent(false);
540         param->setEvaluateOnChange(false);
541         param->setDefault("1 - 1");
542     }
543 } // FrameRangePluginFactory::describeInContext
544 
545 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)546 FrameRangePluginFactory::createInstance(OfxImageEffectHandle handle,
547                                         ContextEnum /*context*/)
548 {
549     return new FrameRangePlugin(handle);
550 }
551 
552 static FrameRangePluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
553 mRegisterPluginFactoryInstance(p)
554 
555 OFXS_NAMESPACE_ANONYMOUS_EXIT
556