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 FrameHold plugin.
21  */
22 
23 #include <cmath>
24 #include <stdio.h> // for snprintf & _snprintf
25 #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
26 #  define NOMINMAX
27 #  include <windows.h>
28 #  if defined(_MSC_VER) && _MSC_VER < 1900
29 #    define snprintf _snprintf
30 #  endif
31 #endif // defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
32 
33 #include "ofxsProcessing.H"
34 #include "ofxsMacros.h"
35 #ifdef OFX_EXTENSIONS_NATRON
36 #include "ofxNatron.h"
37 #endif
38 #include "ofxsThreadSuite.h"
39 
40 using namespace OFX;
41 
42 OFXS_NAMESPACE_ANONYMOUS_ENTER
43 
44 #define kPluginName "FrameHoldOFX"
45 #define kPluginGrouping "Time"
46 #define kPluginDescription "Hold a given frame for the input clip indefinitely, or use a subsample of the input frames and hold them for several frames."
47 #define kPluginIdentifier "net.sf.openfx.FrameHold"
48 #define kPluginVersionMajor 1 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
49 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
50 
51 #define kSupportsTiles 1
52 #define kSupportsMultiResolution 1
53 #define kSupportsRenderScale 1
54 #define kSupportsMultipleClipPARs false
55 #define kSupportsMultipleClipDepths false
56 #define kRenderThreadSafety eRenderFullySafe
57 
58 #define kParamFirstFrame "firstFrame"
59 #define kParamFirstFrameLabel "First Frame"
60 #define kParamFirstFrameHint "Reference input frame (the frame to hold if increment is 0)."
61 #define kParamIncrement "increment"
62 #define kParamIncrementLabel "Increment"
63 #define kParamIncrementHint "If increment is 0, only the \"firstFrame\" will be held. If it is positive, every multiple of \"increment\" plus \"firstFrame\" will be held for \"increment\" frames afterwards (before if it is negative)."
64 
65 ////////////////////////////////////////////////////////////////////////////////
66 /** @brief The plugin that does our work */
67 class FrameHoldPlugin
68     : public ImageEffect
69 {
70 public:
71     /** @brief ctor */
FrameHoldPlugin(OfxImageEffectHandle handle)72     FrameHoldPlugin(OfxImageEffectHandle handle)
73         : ImageEffect(handle)
74         , _dstClip(NULL)
75         , _srcClip(NULL)
76         , _firstFrame(NULL)
77         , _increment(NULL)
78         , _sublabel(NULL)
79     {
80         _dstClip = fetchClip(kOfxImageEffectOutputClipName);
81         assert( _dstClip && (!_dstClip->isConnected() || _dstClip->getPixelComponents() == ePixelComponentAlpha ||
82                              _dstClip->getPixelComponents() == ePixelComponentRGB ||
83                              _dstClip->getPixelComponents() == ePixelComponentRGBA) );
84         _srcClip = getContext() == eContextGenerator ? NULL : fetchClip(kOfxImageEffectSimpleSourceClipName);
85         assert( (!_srcClip && getContext() == eContextGenerator) ||
86                 ( _srcClip && (!_srcClip->isConnected() || _srcClip->getPixelComponents() ==  ePixelComponentAlpha ||
87                                _srcClip->getPixelComponents() == ePixelComponentRGB ||
88                                _srcClip->getPixelComponents() == ePixelComponentRGBA) ) );
89 
90         _firstFrame = fetchIntParam(kParamFirstFrame);
91         _increment = fetchIntParam(kParamIncrement);
92         _sublabel = fetchStringParam(kNatronOfxParamStringSublabelName);
93         assert(_firstFrame && _increment && _sublabel);
94 
95         // finally
96         syncPrivateData();
97     }
98 
99 private:
100     /* Override the render */
101     virtual void render(const RenderArguments &args) OVERRIDE FINAL;
102 
103     /** Override the get frames needed action */
104     virtual void getFramesNeeded(const FramesNeededArguments &args, FramesNeededSetter &frames) OVERRIDE FINAL;
105 
106     /* override is identity */
107     virtual bool isIdentity(const IsIdentityArguments &args, Clip * &identityClip, double &identityTime, int& view, std::string& plane) OVERRIDE FINAL;
108     virtual void changedParam(const InstanceChangedArgs &args, const std::string &paramName) OVERRIDE FINAL;
109     virtual bool getRegionOfDefinition(const RegionOfDefinitionArguments &args, OfxRectD &rod) OVERRIDE FINAL;
110 
111     /** @brief The sync private data action, called when the effect needs to sync any private data to persistent parameters */
syncPrivateData(void)112     virtual void syncPrivateData(void) OVERRIDE FINAL
113     {
114         updateSublabel(0.);
115     }
116 
117 private:
118     double getSourceTime(double time) const;
119 
120     void updateSublabel(double time);
121 
122 private:
123     // do not need to delete these, the ImageEffect is managing them for us
124     Clip *_dstClip;            /**< @brief Mandated output clips */
125     Clip *_srcClip;            /**< @brief Mandated input clips */
126     IntParam  *_firstFrame;
127     IntParam  *_increment;
128     StringParam *_sublabel;
129 };
130 
131 
132 ////////////////////////////////////////////////////////////////////////////////
133 /** @brief render for the filter */
134 
135 ////////////////////////////////////////////////////////////////////////////////
136 // basic plugin render function, just a skelington to instantiate templates from
137 
138 // figure the frame we should be retiming from
139 double
getSourceTime(double t) const140 FrameHoldPlugin::getSourceTime(double t) const
141 {
142     int firstFrame, increment;
143 
144     _firstFrame->getValueAtTime(t, firstFrame);
145     _increment->getValueAtTime(t, increment);
146 
147     if (increment == 0) {
148         return firstFrame;
149     }
150 
151     return firstFrame + increment * std::floor( (t - firstFrame) / increment );
152 }
153 
154 void
updateSublabel(double time)155 FrameHoldPlugin::updateSublabel(double time)
156 {
157     char label[80];
158     int firstFrame, increment;
159 
160     _firstFrame->getValueAtTime(time, firstFrame);
161     _increment->getValueAtTime(time, increment);
162     if (increment == 0) {
163         snprintf(label, sizeof(label), "frame %d", firstFrame);
164     } else {
165         snprintf(label, sizeof(label), "frame %d+n*%d", firstFrame, increment);
166     }
167     _sublabel->setValue(label);
168 }
169 
170 void
getFramesNeeded(const FramesNeededArguments & args,FramesNeededSetter & frames)171 FrameHoldPlugin::getFramesNeeded(const FramesNeededArguments &args,
172                                  FramesNeededSetter &frames)
173 {
174     double sourceTime = getSourceTime(args.time);
175     OfxRangeD range;
176 
177     range.min = sourceTime;
178     range.max = sourceTime;
179     frames.setFramesNeeded(*_srcClip, range);
180 }
181 
182 // the overridden render function
183 void
render(const RenderArguments &)184 FrameHoldPlugin::render(const RenderArguments & /*args*/)
185 {
186     // do nothing as this should never be called as isIdentity should always be trapped
187 }
188 
189 // overridden is identity
190 bool
isIdentity(const IsIdentityArguments & args,Clip * & identityClip,double & identityTime,int &,std::string &)191 FrameHoldPlugin::isIdentity(const IsIdentityArguments &args,
192                             Clip * &identityClip,
193                             double &identityTime
194                             , int& /*view*/, std::string& /*plane*/)
195 {
196     identityClip = _srcClip;
197     identityTime = getSourceTime(args.time);
198 
199     return true;
200 }
201 
202 bool
getRegionOfDefinition(const RegionOfDefinitionArguments & args,OfxRectD & rod)203 FrameHoldPlugin::getRegionOfDefinition(const RegionOfDefinitionArguments &args,
204                                        OfxRectD &rod)
205 {
206     rod = _srcClip->getRegionOfDefinition( getSourceTime(args.time) );
207 
208     return true;
209 }
210 
211 void
changedParam(const InstanceChangedArgs & args,const std::string & paramName)212 FrameHoldPlugin::changedParam(const InstanceChangedArgs &args,
213                               const std::string &paramName)
214 {
215     if ( ( (paramName == kParamFirstFrame) || (paramName == kParamIncrement) ) && (args.reason == eChangeUserEdit) ) {
216         updateSublabel(args.time);
217     }
218 }
219 
220 mDeclarePluginFactory(FrameHoldPluginFactory,; , {});
221 void
load()222 FrameHoldPluginFactory::load()
223 {
224     ofxsThreadSuiteCheck();
225     // we can't be used on hosts that don't perfrom temporal clip access
226     if (!getImageEffectHostDescription()->temporalClipAccess) {
227         throw Exception::HostInadequate("Need random temporal image access to work");
228     }
229 }
230 
231 /** @brief The basic describe function, passed a plugin descriptor */
232 void
describe(ImageEffectDescriptor & desc)233 FrameHoldPluginFactory::describe(ImageEffectDescriptor &desc)
234 {
235     // basic labels
236     desc.setLabel(kPluginName);
237     desc.setPluginGrouping(kPluginGrouping);
238     desc.setPluginDescription(kPluginDescription);
239 
240     // Say we are a filer context
241     desc.addSupportedContext(eContextFilter);
242     desc.addSupportedContext(eContextGeneral);
243 
244     // Add supported pixel depths
245     desc.addSupportedBitDepth(eBitDepthUByte);
246     desc.addSupportedBitDepth(eBitDepthUShort);
247     desc.addSupportedBitDepth(eBitDepthFloat);
248 
249     // set a few flags
250     desc.setSingleInstance(false);
251     desc.setHostFrameThreading(false);
252     desc.setSupportsMultiResolution(kSupportsMultiResolution);
253     desc.setSupportsTiles(kSupportsTiles);
254     desc.setTemporalClipAccess(true); // say we will be doing random time access on clips
255     desc.setRenderTwiceAlways(false);
256     desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
257     desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);
258     desc.setRenderThreadSafety(kRenderThreadSafety);
259     // we can't be used on hosts that don't perfrom temporal clip access
260     if (!getImageEffectHostDescription()->temporalClipAccess) {
261         throw Exception::HostInadequate("Need random temporal image access to work");
262     }
263 #ifdef OFX_EXTENSIONS_NATRON
264     desc.setChannelSelector(ePixelComponentNone);
265 #endif
266 }
267 
268 /** @brief The describe in context function, passed a plugin descriptor and a context */
269 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum)270 FrameHoldPluginFactory::describeInContext(ImageEffectDescriptor &desc,
271                                           ContextEnum /*context*/)
272 {
273     // we are a transition, so define the sourceTo input clip
274     ClipDescriptor *srcClip = desc.defineClip(kOfxImageEffectSimpleSourceClipName);
275 
276     srcClip->addSupportedComponent(ePixelComponentRGBA);
277     srcClip->addSupportedComponent(ePixelComponentRGB);
278     srcClip->addSupportedComponent(ePixelComponentAlpha);
279     srcClip->setTemporalClipAccess(true); // say we will be doing random time access on this clip
280     srcClip->setSupportsTiles(kSupportsTiles);
281 
282     // create the mandated output clip
283     ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
284     dstClip->addSupportedComponent(ePixelComponentRGBA);
285     dstClip->addSupportedComponent(ePixelComponentRGB);
286     dstClip->addSupportedComponent(ePixelComponentAlpha);
287     dstClip->setSupportsTiles(kSupportsTiles);
288 
289     // make some pages and to things in
290     PageParamDescriptor *page = desc.definePageParam("Controls");
291 
292     // firstFrame
293     {
294         IntParamDescriptor *param = desc.defineIntParam(kParamFirstFrame);
295         param->setLabel(kParamFirstFrameLabel);
296         param->setHint(kParamFirstFrameHint);
297         param->setDefault(0);
298         param->setRange(INT_MIN, INT_MAX);
299         param->setDisplayRange(INT_MIN, INT_MAX);
300         param->setAnimates(true);
301         if (page) {
302             page->addChild(*param);
303         }
304     }
305 
306     // increment
307     {
308         IntParamDescriptor *param = desc.defineIntParam(kParamIncrement);
309         param->setLabel(kParamIncrementLabel);
310         param->setHint(kParamIncrementHint);
311         param->setDefault(0);
312         param->setRange(0, INT_MAX);
313         param->setDisplayRange(0, INT_MAX);
314         param->setAnimates(true);
315         if (page) {
316             page->addChild(*param);
317         }
318     }
319 
320     // sublabel
321     {
322         StringParamDescriptor* param = desc.defineStringParam(kNatronOfxParamStringSublabelName);
323         param->setIsSecretAndDisabled(true); // always secret
324         param->setIsPersistent(false);
325         param->setEvaluateOnChange(false);
326         param->setDefault("frame 0");
327         if (page) {
328             page->addChild(*param);
329         }
330     }
331 } // FrameHoldPluginFactory::describeInContext
332 
333 /** @brief The create instance function, the plugin must return an object derived from the \ref ImageEffect class */
334 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)335 FrameHoldPluginFactory::createInstance(OfxImageEffectHandle handle,
336                                        ContextEnum /*context*/)
337 {
338     return new FrameHoldPlugin(handle);
339 }
340 
341 static FrameHoldPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
342 mRegisterPluginFactoryInstance(p)
343 
344 OFXS_NAMESPACE_ANONYMOUS_EXIT
345