1 /*
2 OFX retimer example plugin, a plugin that illustrates the use of the OFX Support library.
3
4 This will not work very well on fielded imagery.
5
6 Copyright (C) 2004-2005 The Open Effects Association Ltd
7 Author Bruno Nicoletti bruno@thefoundry.co.uk
8
9 Redistribution and use in source and binary forms, with or without
10 modification, are permitted provided that the following conditions are met:
11
12 * Redistributions of source code must retain the above copyright notice,
13 this list of conditions and the following disclaimer.
14 * Redistributions in binary form must reproduce the above copyright notice,
15 this list of conditions and the following disclaimer in the documentation
16 and/or other materials provided with the distribution.
17 * Neither the name The Open Effects Association Ltd, nor the names of its
18 contributors may be used to endorse or promote products derived from this
19 software without specific prior written permission.
20
21 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
22 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
25 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
28 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
32 The Open Effects Association Ltd
33 1 Wardour St
34 London W1D 6PA
35 England
36
37
38 */
39
40 #include <math.h> // for floor
41 #include <float.h> // for FLT_MAX
42
43 #include <stdio.h>
44 #include "ofxsImageEffect.h"
45 #include "ofxsMultiThread.h"
46
47 #include "../include/ofxsProcessing.H"
48 #include "../include/ofxsImageBlender.H"
49
50 namespace OFX {
51 extern ImageEffectHostDescription gHostDescription;
52 }
53 ////////////////////////////////////////////////////////////////////////////////
54 /** @brief The plugin that does our work */
55 class RetimerPlugin : public OFX::ImageEffect {
56 protected :
57 // do not need to delete these, the ImageEffect is managing them for us
58 OFX::Clip *dstClip_; /**< @brief Mandated output clips */
59 OFX::Clip *srcClip_; /**< @brief Mandated input clips */
60
61 OFX::DoubleParam *sourceTime_; /**< @brief mandated parameter, only used in the retimer context. */
62 OFX::DoubleParam *speed_; /**< @brief only used in the filter context. */
63 OFX::DoubleParam *duration_; /**< @brief how long the output should be as a proportion of input. General context only */
64
65 public :
66 /** @brief ctor */
RetimerPlugin(OfxImageEffectHandle handle)67 RetimerPlugin(OfxImageEffectHandle handle)
68 : ImageEffect(handle)
69 , dstClip_(NULL)
70 , srcClip_(NULL)
71 , sourceTime_(NULL)
72 , speed_(NULL)
73 , duration_(NULL)
74 {
75 dstClip_ = fetchClip(kOfxImageEffectOutputClipName);
76 srcClip_ = fetchClip(kOfxImageEffectSimpleSourceClipName);
77
78 // What parameters we instantiate depend on the context
79 if(getContext() == OFX::eContextRetimer)
80 // fetch the mandated parameter which the host uses to pass us the frame to retime to
81 sourceTime_ = fetchDoubleParam(kOfxImageEffectRetimerParamName);
82 else // context == OFX::eContextFilter || context == OFX::eContextGeneral
83 // filter context means we are in charge of how to retime, and our example is using a speed curve to do that
84 speed_ = fetchDoubleParam("Speed");
85
86 // fetch duration param for general context
87 if(getContext() == OFX::eContextGeneral)
88 duration_ = fetchDoubleParam("Duration");
89 }
90
91 /* Override the render */
92 virtual void render(const OFX::RenderArguments &args);
93
94 /** Override the get frames needed action */
95 virtual void getFramesNeeded(const OFX::FramesNeededArguments &args, OFX::FramesNeededSetter &frames);
96
97 /* override the time domain action, only for the general context */
98 virtual bool getTimeDomain(OfxRangeD &range);
99
100 /* set up and run a processor */
101 void
102 setupAndProcess(OFX::ImageBlenderBase &, const OFX::RenderArguments &args);
103 };
104
105
106 ////////////////////////////////////////////////////////////////////////////////
107 /** @brief render for the filter */
108
109 ////////////////////////////////////////////////////////////////////////////////
110 // basic plugin render function, just a skelington to instantiate templates from
111
112 // make sure components are sane
113 static void
checkComponents(const OFX::Image & src,OFX::BitDepthEnum dstBitDepth,OFX::PixelComponentEnum dstComponents)114 checkComponents(const OFX::Image &src,
115 OFX::BitDepthEnum dstBitDepth,
116 OFX::PixelComponentEnum dstComponents)
117 {
118 OFX::BitDepthEnum srcBitDepth = src.getPixelDepth();
119 OFX::PixelComponentEnum srcComponents = src.getPixelComponents();
120
121 // see if they have the same depths and bytes and all
122 if(srcBitDepth != dstBitDepth || srcComponents != dstComponents)
123 throw int(1); // HACK!! need to throw an sensible exception here!
124 }
125
framesNeeded(double sourceTime,OFX::FieldEnum fieldToRender,double * fromTimep,double * toTimep,double * blendp)126 static void framesNeeded(double sourceTime, OFX::FieldEnum fieldToRender, double *fromTimep, double *toTimep, double *blendp)
127 {
128 // figure the two images we are blending between
129 double fromTime, toTime;
130 double blend;
131
132 if (fieldToRender == OFX::eFieldNone) {
133 // unfielded, easy peasy
134 fromTime = floor(sourceTime);
135 toTime = fromTime + 1;
136 blend = sourceTime - fromTime;
137 }
138 else {
139 // Fielded clips, pook. We are rendering field doubled images,
140 // and so need to blend between fields, not frames.
141 double frac = sourceTime - floor(sourceTime);
142 if(frac < 0.5) {
143 // need to go between the first and second fields of this frame
144 fromTime = floor(sourceTime); // this will get the first field
145 toTime = fromTime + 0.5; // this will get the second field of the same frame
146 blend = frac * 2.0; // and the blend is between those two
147 }
148 else { // frac > 0.5
149 fromTime = floor(sourceTime) + 0.5; // this will get the second field of this frame
150 toTime = floor(sourceTime) + 1.0; // this will get the first field of the next frame
151 blend = (frac - 0.5) * 2.0;
152 }
153 }
154 *fromTimep = fromTime;
155 *toTimep = toTime;
156 *blendp = blend;
157 }
158
159 /* set up and run a processor */
160 void
setupAndProcess(OFX::ImageBlenderBase & processor,const OFX::RenderArguments & args)161 RetimerPlugin::setupAndProcess(OFX::ImageBlenderBase &processor, const OFX::RenderArguments &args)
162 {
163 // get a dst image
164 OFX::auto_ptr<OFX::Image> dst(dstClip_->fetchImage(args.time));
165 OFX::BitDepthEnum dstBitDepth = dst->getPixelDepth();
166 OFX::PixelComponentEnum dstComponents = dst->getPixelComponents();
167
168 // figure the frame we should be retiming from
169 double sourceTime;
170
171 if(getContext() == OFX::eContextRetimer) {
172 // the host is specifying it, so fetch it from the kOfxImageEffectRetimerParamName pseudo-param
173 sourceTime = sourceTime_->getValueAtTime(args.time);
174 }
175 else {
176 // we have our own param, which is a speed, so we integrate it to get the time we want
177 sourceTime = speed_->integrate(0, args.time);
178 }
179
180 // figure the two images we are blending between
181 double fromTime, toTime;
182 double blend;
183 framesNeeded(sourceTime, args.fieldToRender, &fromTime, &toTime, &blend);
184
185 // fetch the two source images
186 OFX::auto_ptr<OFX::Image> fromImg(srcClip_->fetchImage(fromTime));
187 OFX::auto_ptr<OFX::Image> toImg(srcClip_->fetchImage(toTime));
188
189 // make sure bit depths are sane
190 if(fromImg.get()) checkComponents(*fromImg, dstBitDepth, dstComponents);
191 if(toImg.get()) checkComponents(*toImg, dstBitDepth, dstComponents);
192
193 // set the images
194 processor.setDstImg(dst.get());
195 processor.setFromImg(fromImg.get());
196 processor.setToImg(toImg.get());
197
198 // set the render window
199 processor.setRenderWindow(args.renderWindow);
200
201 // set the blend between
202 processor.setBlend((float)blend);
203
204 // Call the base class process member, this will call the derived templated process code
205 processor.process();
206 }
207
208 void
getFramesNeeded(const OFX::FramesNeededArguments & args,OFX::FramesNeededSetter & frames)209 RetimerPlugin::getFramesNeeded(const OFX::FramesNeededArguments &args,
210 OFX::FramesNeededSetter &frames)
211 {
212 // figure the two images we are blending between
213 double fromTime, toTime;
214 double blend;
215 // whatever the rendered field is, the frames are the same
216 framesNeeded(args.time, OFX::eFieldNone, &fromTime, &toTime, &blend);
217 OfxRangeD range;
218 range.min = fromTime;
219 range.max = toTime;
220 frames.setFramesNeeded(*srcClip_, range);
221 }
222
223 /* override the time domain action, only for the general context */
224 bool
getTimeDomain(OfxRangeD & range)225 RetimerPlugin::getTimeDomain(OfxRangeD &range)
226 {
227 // this should only be called in the general context, ever!
228 if(getContext() == OFX::eContextGeneral) {
229 // If we are a general context, we can changed the duration of the effect, so have a param to do that
230 // We need a separate param as it is impossible to derive this from a speed param and the input clip
231 // duration (the speed may be animating or wired to an expression).
232 double duration = duration_->getValue(); //don't animate
233
234 // how many frames on the input clip
235 OfxRangeD srcRange = srcClip_->getFrameRange();
236
237 range.min = 0;
238 range.max = srcRange.max * duration;
239 return true;
240 }
241
242 return false;
243 }
244
245 // the overridden render function
246 void
render(const OFX::RenderArguments & args)247 RetimerPlugin::render(const OFX::RenderArguments &args)
248 {
249 // instantiate the render code based on the pixel depth of the dst clip
250 OFX::BitDepthEnum dstBitDepth = dstClip_->getPixelDepth();
251 OFX::PixelComponentEnum dstComponents = dstClip_->getPixelComponents();
252
253 // do the rendering
254 if(dstComponents == OFX::ePixelComponentRGBA) {
255 switch(dstBitDepth) {
256 case OFX::eBitDepthUByte : {
257 OFX::ImageBlender<unsigned char, 4> fred(*this);
258 setupAndProcess(fred, args);
259 }
260 break;
261
262 case OFX::eBitDepthUShort : {
263 OFX::ImageBlender<unsigned short, 4> fred(*this);
264 setupAndProcess(fred, args);
265 }
266 break;
267
268 case OFX::eBitDepthFloat : {
269 OFX::ImageBlender<float, 4> fred(*this);
270 setupAndProcess(fred, args);
271 }
272 break;
273 default :
274 OFX::throwSuiteStatusException(kOfxStatErrUnsupported);
275 }
276 }
277 else {
278 switch(dstBitDepth) {
279 case OFX::eBitDepthUByte : {
280 OFX::ImageBlender<unsigned char, 1> fred(*this);
281 setupAndProcess(fred, args);
282 }
283 break;
284
285 case OFX::eBitDepthUShort : {
286 OFX::ImageBlender<unsigned short, 1> fred(*this);
287 setupAndProcess(fred, args);
288 }
289 break;
290
291 case OFX::eBitDepthFloat : {
292 OFX::ImageBlender<float, 1> fred(*this);
293 setupAndProcess(fred, args);
294 }
295 break;
296 default :
297 OFX::throwSuiteStatusException(kOfxStatErrUnsupported);
298 }
299 } // switch
300 }
301
302 using namespace OFX;
303 mDeclarePluginFactory(RetimerExamplePluginFactory, ;, {});
304
305 static RetimerExamplePluginFactory p("net.sf.openfx.retimer", 1, 0);
mRegisterPluginFactoryInstance(p)306 mRegisterPluginFactoryInstance(p)
307
308 void RetimerExamplePluginFactory::load()
309 {
310 // we can't be used on hosts that don't perfrom temporal clip access
311 if(!gHostDescription.temporalClipAccess) {
312 throw OFX::Exception::HostInadequate("Need random temporal image access to work");
313 }
314 }
315
316 /** @brief The basic describe function, passed a plugin descriptor */
describe(OFX::ImageEffectDescriptor & desc)317 void RetimerExamplePluginFactory::describe(OFX::ImageEffectDescriptor &desc)
318 {
319 // basic labels
320 desc.setLabels("Retimer", "Retimer", "Retimer");
321 desc.setPluginGrouping("OFX");
322
323 // Say we are a transition context
324 desc.addSupportedContext(OFX::eContextRetimer);
325 desc.addSupportedContext(OFX::eContextFilter);
326 desc.addSupportedContext(OFX::eContextGeneral);
327
328 // Add supported pixel depths
329 desc.addSupportedBitDepth(eBitDepthUByte);
330 desc.addSupportedBitDepth(eBitDepthUShort);
331 desc.addSupportedBitDepth(eBitDepthFloat);
332
333 // set a few flags
334 desc.setSingleInstance(false);
335 desc.setHostFrameThreading(false);
336 desc.setSupportsMultiResolution(true);
337 desc.setSupportsTiles(true);
338 desc.setTemporalClipAccess(true); // say we will be doing random time access on clips
339 desc.setRenderTwiceAlways(false);
340 desc.setSupportsMultipleClipPARs(false);
341 }
342
343 /** @brief The describe in context function, passed a plugin descriptor and a context */
describeInContext(OFX::ImageEffectDescriptor & desc,ContextEnum context)344 void RetimerExamplePluginFactory::describeInContext(OFX::ImageEffectDescriptor &desc, ContextEnum context)
345 {
346 // we are a transition, so define the sourceTo input clip
347 ClipDescriptor *srcClip = desc.defineClip(kOfxImageEffectSimpleSourceClipName);
348 srcClip->addSupportedComponent(ePixelComponentRGBA);
349 srcClip->addSupportedComponent(ePixelComponentAlpha);
350 srcClip->setTemporalClipAccess(true); // say we will be doing random time access on this clip
351 srcClip->setSupportsTiles(true);
352 srcClip->setFieldExtraction(eFieldExtractDoubled); // which is the default anyway
353
354 // create the mandated output clip
355 ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
356 dstClip->addSupportedComponent(ePixelComponentRGBA);
357 dstClip->addSupportedComponent(ePixelComponentAlpha);
358 dstClip->setFieldExtraction(eFieldExtractDoubled); // which is the default anyway
359 dstClip->setSupportsTiles(true);
360
361 // what param we have is dependant on the host
362 if(context == OFX::eContextRetimer) {
363 // Define the mandated kOfxImageEffectRetimerParamName param, note that we don't do anything with this other than.
364 // describe it. It is not a true param but how the host indicates to the plug-in which frame
365 // it wants you to retime to. It appears on no plug-in side UI, it is purely the host's to manage.
366 DoubleParamDescriptor *param = desc.defineDoubleParam(kOfxImageEffectRetimerParamName);
367 (void)param;
368 }
369 else {
370 // We are a general or filter context, define a speed param and a page of controls to put that in
371 DoubleParamDescriptor *param = desc.defineDoubleParam("Speed");
372 param->setLabels("speed", "speed", "speed");
373 param->setScriptName("speed");
374 param->setHint("How much to changed the speed of the input clip");
375 param->setDefault(1);
376 param->setRange(-FLT_MAX, FLT_MAX);
377 param->setIncrement(0.05);
378 param->setDisplayRange(0, 1);
379 param->setAnimates(true); // can animate
380 param->setDoubleType(eDoubleTypeScale);
381
382 // make a page to put it in
383 PageParamDescriptor *page = desc.definePageParam("Controls");
384
385 // add our speed param into it
386 page->addChild(*param);
387
388 // If we are a general context, we can change the duration of the effect, so have a param to do that
389 // We need a separate param as it is impossible to derive this from a speed param and the input clip
390 // duration (the speed may be animating or wired to an expression).
391 if(context == OFX::eContextGeneral) {
392 // We are a general or filter context, define a speed param and a page of controls to put that in
393 DoubleParamDescriptor *param = desc.defineDoubleParam("Duration");
394 param->setLabels("duration", "duraction", "duration");
395 param->setScriptName("duration");
396 param->setHint("How long the output clip should be, as a proportion of the input clip's length.");
397 param->setDefault(1);
398 param->setRange(0, 10);
399 param->setIncrement(0.1);
400 param->setDisplayRange(0, 10);
401 param->setAnimates(false); // no animation here!
402 param->setDoubleType(eDoubleTypeScale);
403
404 // add param to page
405 page->addChild(*param);
406 }
407 }
408 }
409
410 /** @brief The create instance function, the plugin must return an object derived from the \ref OFX::ImageEffect class */
createInstance(OfxImageEffectHandle handle,ContextEnum)411 ImageEffect* RetimerExamplePluginFactory::createInstance(OfxImageEffectHandle handle, ContextEnum /*context*/)
412 {
413 return new RetimerPlugin(handle);
414 }
415