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 ¶mName) 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 ¶mName)
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