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