1 /*
2 Software License :
3 
4 Copyright (c) 2014, The Open Effects Association Ltd. All rights reserved.
5 
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions are met:
8 
9     * Redistributions of source code must retain the above copyright notice,
10       this list of conditions and the following disclaimer.
11     * Redistributions in binary form must reproduce the above copyright notice,
12       this list of conditions and the following disclaimer in the documentation
13       and/or other materials provided with the distribution.
14     * Neither the name The Open Effects Association Ltd, nor the names of its
15       contributors may be used to endorse or promote products derived from this
16       software without specific prior written permission.
17 
18 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29 
30 /*
31   Author : Bruno Nicoletti (2014)
32 
33   This plugin will take you through the basics of defining and using
34   parameters as well as how to use instance data.
35 
36   The accompanying guide will explain what is happening in more detail.
37 */
38 
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <math.h>
43 
44 #include <string>
45 #include <iostream>
46 
47 // the one OFX header we need, it includes the others necessary
48 #include "ofxImageEffect.h"
49 
50 #if defined __APPLE__ || defined linux || defined __DragonFly__
51 #  define EXPORT __attribute__((visibility("default")))
52 #elif defined _WIN32
53 #  define EXPORT OfxExport
54 #else
55 #  error Not building on your operating system quite yet
56 #endif
57 
58 ////////////////////////////////////////////////////////////////////////////////
59 // macro to write a labelled message to stderr with
60 #ifdef _WIN32
61   #define DUMP(LABEL, MSG, ...)                                           \
62   {                                                                       \
63     fprintf(stderr, "%s%s:%d in %s ", LABEL, __FILE__, __LINE__, __FUNCTION__); \
64     fprintf(stderr, MSG, ##__VA_ARGS__);                                  \
65     fprintf(stderr, "\n");                                                \
66   }
67 #else
68   #define DUMP(LABEL, MSG, ...)                                           \
69   {                                                                       \
70     fprintf(stderr, "%s%s:%d in %s ", LABEL, __FILE__, __LINE__, __PRETTY_FUNCTION__); \
71     fprintf(stderr, MSG, ##__VA_ARGS__);                                  \
72     fprintf(stderr, "\n");                                                \
73   }
74 #endif
75 
76 // macro to write a simple message, only works if 'VERBOSE' is #defined
77 //#define VERBOSE
78 #ifdef VERBOSE
79 #  define MESSAGE(MSG, ...) DUMP("", MSG, ##__VA_ARGS__)
80 #else
81 #  define MESSAGE(MSG, ...)
82 #endif
83 
84 // macro to dump errors to stderr if the given condition is true
85 #define ERROR_IF(CONDITION, MSG, ...) if(CONDITION) { DUMP("ERROR : ", MSG, ##__VA_ARGS__);}
86 
87 // macro to dump errors to stderr and abort if the given condition is true
88 #define ERROR_ABORT_IF(CONDITION, MSG, ...)     \
89 {                                               \
90   if(CONDITION) {                               \
91     DUMP("FATAL ERROR : ", MSG, ##__VA_ARGS__); \
92     abort();                                    \
93   }                                             \
94 }
95 
96 // name of our params
97 #define RADIUS_PARAM_NAME "radius"
98 #define CENTRE_PARAM_NAME "centre"
99 #define COLOUR_PARAM_NAME "colour"
100 #define GROW_ROD_PARAM_NAME "growRoD"
101 
102 // anonymous namespace to hide our symbols in
103 namespace {
104   ////////////////////////////////////////////////////////////////////////////////
105   // set of suite pointers provided by the host
106   OfxHost               *gHost;
107   OfxPropertySuiteV1    *gPropertySuite    = 0;
108   OfxImageEffectSuiteV1 *gImageEffectSuite = 0;
109   OfxParameterSuiteV1   *gParameterSuite   = 0;
110 
111   // version of the API the host is running
112   int gAPIVersion[2] = {1, 0};
113 
114   // does the host support multi resolution images
115   int gHostSupportsMultiRes = false;
116 
117   ////////////////////////////////////////////////////////////////////////////////
118   // class to manage OFX images
119   class Image {
120   public    :
121     // construct from a property set that represents the image
122     Image(OfxPropertySetHandle propSet);
123 
124     // construct from a clip by fetching an image at the given frame
125     Image(OfxImageClipHandle clip, double frame);
126 
127     // destructor
128     ~Image();
129 
130     // get a pixel address, cast to the right type
131     template <class T>
pixelAddress(int x,int y)132     T *pixelAddress(int x, int y)
133     {
134       return reinterpret_cast<T *>(rawAddress(x, y));
135     }
136 
137     // get a pixel address, if it doesn't exist
138     // return a default black pixel
139     template <class T>
pixelAddressWithFallback(int x,int y)140     const T *pixelAddressWithFallback(int x, int y)
141     {
142       const T *pix = pixelAddress<T>(x, y);
143       if(!pix) {
144         static const T blackPix[4] = {0,0,0,0};
145         pix = blackPix;
146       }
147       return pix;
148     }
149 
150     // Is this image empty?
151     operator bool();
152 
153     // bytes per component, 1, 2 or 4 for byte, short and float images
bytesPerComponent() const154     int bytesPerComponent() const { return bytesPerComponent_; }
155 
156     // number of components
nComponents() const157     int nComponents() const { return nComponents_; }
158 
159     // number of components
pixelAspectRatio() const160     double pixelAspectRatio() const { return pixelAspectRatio_; }
161 
162   protected :
163     void construct();
164 
165     // Look up a pixel address in the image. returns null if the pixel was not
166     // in the bounds of the image
167     void *rawAddress(int x, int y);
168 
169     OfxPropertySetHandle propSet_;
170     int rowBytes_;
171     OfxRectI bounds_;
172     char *dataPtr_;
173     int nComponents_;
174     int bytesPerComponent_;
175     int bytesPerPixel_;
176     double pixelAspectRatio_;
177   };
178 
179   // construct from a property set
Image(OfxPropertySetHandle propSet)180   Image::Image(OfxPropertySetHandle propSet)
181     : propSet_(propSet)
182   {
183     construct();
184   }
185 
186   // construct by fetching from a clip
Image(OfxImageClipHandle clip,double time)187   Image::Image(OfxImageClipHandle clip, double time)
188     : propSet_(NULL)
189   {
190     if (clip && (gImageEffectSuite->clipGetImage(clip, time, NULL, &propSet_) == kOfxStatOK)) {
191       construct();
192     }
193     else {
194       propSet_ = NULL;
195       rowBytes_ = 0;
196       bounds_.x1 = bounds_.x2 = bounds_.y1 = bounds_.y2 = 0;
197       dataPtr_ = NULL;
198       nComponents_ = 0;
199       bytesPerComponent_ = 0;
200       bytesPerPixel_ = 0;
201       pixelAspectRatio_ = 1.;
202     }
203   }
204 
205   // assemble it all togther
construct()206   void Image::construct()
207   {
208     if(propSet_) {
209       gPropertySuite->propGetInt(propSet_, kOfxImagePropRowBytes, 0, &rowBytes_);
210       gPropertySuite->propGetIntN(propSet_, kOfxImagePropBounds, 4, &bounds_.x1);
211       gPropertySuite->propGetPointer(propSet_, kOfxImagePropData, 0, (void **) &dataPtr_);
212       gPropertySuite->propGetDouble(propSet_, kOfxImagePropPixelAspectRatio, 0, &pixelAspectRatio_);
213 
214       // how many components per pixel?
215       const char *cstr;
216       gPropertySuite->propGetString(propSet_, kOfxImageEffectPropComponents, 0, &cstr);
217 
218       if(strcmp(cstr, kOfxImageComponentRGBA) == 0) {
219         nComponents_ = 4;
220       }
221       else if(strcmp(cstr, kOfxImageComponentRGB) == 0) {
222         nComponents_ = 3;
223       }
224       else if(strcmp(cstr, kOfxImageComponentAlpha) == 0) {
225         nComponents_ = 1;
226       }
227       else {
228         throw " bad pixel type!";
229       }
230 
231       // what is the data type
232       gPropertySuite->propGetString(propSet_, kOfxImageEffectPropPixelDepth, 0, &cstr);
233       if(strcmp(cstr, kOfxBitDepthByte) == 0) {
234         bytesPerComponent_ = 1;
235       }
236       else if(strcmp(cstr, kOfxBitDepthShort) == 0) {
237         bytesPerComponent_ = 2;
238       }
239       else if(strcmp(cstr, kOfxBitDepthFloat) == 0) {
240         bytesPerComponent_ = 4;
241       }
242       else {
243         throw " bad pixel type!";
244       }
245 
246       bytesPerPixel_ = bytesPerComponent_ * nComponents_;
247     }
248     else {
249       rowBytes_ = 0;
250       bounds_.x1 = bounds_.x2 = bounds_.y1 = bounds_.y2 = 0;
251       dataPtr_ = NULL;
252       nComponents_ = 0;
253       bytesPerComponent_ = 0;
254       bytesPerPixel_ = 0;
255       pixelAspectRatio_ = 1.;
256     }
257   }
258 
259   // destructor
~Image()260   Image::~Image()
261   {
262     if(propSet_)
263       gImageEffectSuite->clipReleaseImage(propSet_);
264   }
265 
266   // get the address of a location in the image as a void *
rawAddress(int x,int y)267   void *Image::rawAddress(int x, int y)
268   {
269     // Inside the bounds of this image?
270     if(x < bounds_.x1 || x >= bounds_.x2 || y < bounds_.y1 || y >= bounds_.y2)
271       return NULL;
272 
273     // turn image plane coordinates into offsets from the bottom left
274     int yOffset = y - bounds_.y1;
275     int xOffset = x - bounds_.x1;
276 
277     // Find the start of our row, using byte arithmetic
278     char *rowStart = (dataPtr_) + yOffset * rowBytes_;
279 
280     // finally find the position of the first component of column
281     return rowStart + (xOffset * bytesPerPixel_);
282   }
283 
284   // are we empty?
operator bool()285   Image:: operator bool()
286   {
287     return propSet_ != NULL && dataPtr_ != NULL;
288   }
289 
290   ////////////////////////////////////////////////////////////////////////////////
291   // our instance data, where we are caching away clip and param handles
292   struct MyInstanceData {
293     // are we in the general context
294     bool isGeneralContext;
295 
296     // handles to the clips we deal with
297     OfxImageClipHandle sourceClip;
298     OfxImageClipHandle outputClip;
299 
300     // handles to a our parameters
301     OfxParamHandle centreParam;
302     OfxParamHandle radiusParam;
303     OfxParamHandle colourParam;
304     OfxParamHandle growRoD;
305 
MyInstanceData__anonee2331770111::MyInstanceData306     MyInstanceData()
307       : isGeneralContext(false)
308       , sourceClip(NULL)
309       , outputClip(NULL)
310       , centreParam(NULL)
311       , radiusParam(NULL)
312       , colourParam(NULL)
313       , growRoD(NULL)
314     {}
315   };
316 
317   ////////////////////////////////////////////////////////////////////////////////
318   // get my instance data from a property set handle
FetchInstanceData(OfxPropertySetHandle effectProps)319   MyInstanceData *FetchInstanceData(OfxPropertySetHandle effectProps)
320   {
321     MyInstanceData *myData = 0;
322     gPropertySuite->propGetPointer(effectProps,
323                                    kOfxPropInstanceData,
324                                    0,
325                                    (void **) &myData);
326     return myData;
327   }
328 
329   ////////////////////////////////////////////////////////////////////////////////
330   // get my instance data
FetchInstanceData(OfxImageEffectHandle effect)331   MyInstanceData *FetchInstanceData(OfxImageEffectHandle effect)
332   {
333     // get the property handle for the plugin
334     OfxPropertySetHandle effectProps;
335     gImageEffectSuite->getPropertySet(effect, &effectProps);
336 
337     // and get the instance data out of that
338     return FetchInstanceData(effectProps);
339   }
340 
341   ////////////////////////////////////////////////////////////////////////////////
342   // get the named suite and put it in the given pointer, with error checking
343   template <class SUITE>
FetchSuite(SUITE * & suite,const char * suiteName,int suiteVersion)344   void FetchSuite(SUITE *& suite, const char *suiteName, int suiteVersion)
345   {
346     suite = (SUITE *) gHost->fetchSuite(gHost->host, suiteName, suiteVersion);
347     if(!suite) {
348       ERROR_ABORT_IF(suite == NULL,
349                      "Failed to fetch %s verison %d from the host.",
350                      suiteName,
351                      suiteVersion);
352     }
353   }
354 
355   ////////////////////////////////////////////////////////////////////////////////
356   // The first _action_ called after the binary is loaded (three boot strapper functions will be howeever)
LoadAction(void)357   OfxStatus LoadAction(void)
358   {
359     // fetch our three suites
360     FetchSuite(gPropertySuite,    kOfxPropertySuite,    1);
361     FetchSuite(gImageEffectSuite, kOfxImageEffectSuite, 1);
362     FetchSuite(gParameterSuite,   kOfxParameterSuite,   1);
363 
364     int verSize = 0;
365     if(gPropertySuite->propGetDimension(gHost->host, kOfxPropAPIVersion, &verSize) == kOfxStatOK) {
366       verSize = verSize > 2 ? 2 : verSize;
367       gPropertySuite->propGetIntN(gHost->host,
368                                   kOfxPropAPIVersion,
369                                   2,
370                                   gAPIVersion);
371     }
372 
373     // we only support 1.2 and above
374     if(gAPIVersion[0] == 1 && gAPIVersion[1] < 2) {
375       return kOfxStatFailed;
376     }
377 
378     /// does the host support multi-resolution images
379     gPropertySuite->propGetInt(gHost->host,
380                                kOfxImageEffectPropSupportsMultiResolution,
381                                0,
382                                &gHostSupportsMultiRes);
383 
384     return kOfxStatOK;
385   }
386 
387   ////////////////////////////////////////////////////////////////////////////////
388   // the plugin's basic description routine
DescribeAction(OfxImageEffectHandle descriptor)389   OfxStatus DescribeAction(OfxImageEffectHandle descriptor)
390   {
391     // get the property set handle for the plugin
392     OfxPropertySetHandle effectProps;
393     gImageEffectSuite->getPropertySet(descriptor, &effectProps);
394 
395     // set some labels and the group it belongs to
396     gPropertySuite->propSetString(effectProps,
397                                   kOfxPropLabel,
398                                   0,
399                                   "OFX Circle Example");
400     gPropertySuite->propSetString(effectProps,
401                                   kOfxImageEffectPluginPropGrouping,
402                                   0,
403                                   "OFX Example");
404 
405     // define the image effects contexts we can be used in, in this case a filter
406     // and a general effect
407     gPropertySuite->propSetString(effectProps,
408                                   kOfxImageEffectPropSupportedContexts,
409                                   0,
410                                   kOfxImageEffectContextFilter);
411 
412     // set the bit depths the plugin can handle
413     gPropertySuite->propSetString(effectProps,
414                                   kOfxImageEffectPropSupportedPixelDepths,
415                                   0,
416                                   kOfxBitDepthFloat);
417     gPropertySuite->propSetString(effectProps,
418                                   kOfxImageEffectPropSupportedPixelDepths,
419                                   1,
420                                   kOfxBitDepthShort);
421     gPropertySuite->propSetString(effectProps,
422                                   kOfxImageEffectPropSupportedPixelDepths,
423                                   2,
424                                   kOfxBitDepthByte);
425 
426     // say that a single instance of this plugin can be rendered in multiple threads
427     gPropertySuite->propSetString(effectProps,
428                                   kOfxImageEffectPluginRenderThreadSafety,
429                                   0,
430                                   kOfxImageEffectRenderFullySafe);
431 
432     // say that the host should manage SMP threading over a single frame
433     gPropertySuite->propSetInt(effectProps,
434                                kOfxImageEffectPluginPropHostFrameThreading,
435                                0,
436                                1);
437     return kOfxStatOK;
438   }
439 
440   ////////////////////////////////////////////////////////////////////////////////
441   //  describe the plugin in context
442   OfxStatus
DescribeInContextAction(OfxImageEffectHandle descriptor,OfxPropertySetHandle inArgs)443   DescribeInContextAction(OfxImageEffectHandle descriptor,
444                           OfxPropertySetHandle inArgs)
445   {
446     // get the context we are being described for
447     const char *context;
448     gPropertySuite->propGetString(inArgs, kOfxImageEffectPropContext, 0, &context);
449 
450     // what components do we support
451     static const char *supportedComponents[] = {kOfxImageComponentRGBA, kOfxImageComponentRGB, kOfxImageComponentAlpha};
452 
453     OfxPropertySetHandle props;
454     // define the mandated single output clip
455     gImageEffectSuite->clipDefine(descriptor, "Output", &props);
456 
457     // set the component types we can handle on out output
458     gPropertySuite->propSetStringN(props,
459                                    kOfxImageEffectPropSupportedComponents,
460                                    3,
461                                    supportedComponents);
462 
463     // define the mandated single source clip
464     gImageEffectSuite->clipDefine(descriptor, "Source", &props);
465 
466     // set the component types we can handle on our main input
467     gPropertySuite->propSetStringN(props,
468                                    kOfxImageEffectPropSupportedComponents,
469                                    3,
470                                    supportedComponents);
471 
472     // first get the handle to the parameter set
473     OfxParamSetHandle paramSet;
474     gImageEffectSuite->getParamSet(descriptor, &paramSet);
475 
476     // properties on our parameter
477     OfxPropertySetHandle radiusParamProps;
478 
479     // set the properties on the radius param
480     gParameterSuite->paramDefine(paramSet,
481                                  kOfxParamTypeDouble,
482                                  RADIUS_PARAM_NAME,
483                                  &radiusParamProps);
484 
485     gPropertySuite->propSetString(radiusParamProps,
486                                   kOfxParamPropDoubleType,
487                                   0,
488                                   kOfxParamDoubleTypeX);
489 
490     gPropertySuite->propSetString(radiusParamProps,
491                                   kOfxParamPropDefaultCoordinateSystem,
492                                   0,
493                                   kOfxParamCoordinatesNormalised);
494 
495     gPropertySuite->propSetDouble(radiusParamProps,
496                                   kOfxParamPropDefault,
497                                   0,
498                                   0.25);
499     gPropertySuite->propSetDouble(radiusParamProps,
500                                   kOfxParamPropMin,
501                                   0,
502                                   0);
503     gPropertySuite->propSetDouble(radiusParamProps,
504                                   kOfxParamPropDisplayMin,
505                                   0,
506                                   0.0);
507     gPropertySuite->propSetDouble(radiusParamProps,
508                                   kOfxParamPropDisplayMax,
509                                   0,
510                                   2.0);
511     gPropertySuite->propSetString(radiusParamProps,
512                                   kOfxPropLabel,
513                                   0,
514                                   "Radius");
515     gPropertySuite->propSetString(radiusParamProps,
516                                   kOfxParamPropHint,
517                                   0,
518                                   "The radius of the circle.");
519 
520     // set the properties on the centre param
521     OfxPropertySetHandle centreParamProps;
522     static double centreDefault[] = {0.5, 0.5};
523 
524     gParameterSuite->paramDefine(paramSet,
525                                  kOfxParamTypeDouble2D,
526                                  CENTRE_PARAM_NAME,
527                                  &centreParamProps);
528 
529     gPropertySuite->propSetString(centreParamProps,
530                                   kOfxParamPropDoubleType,
531                                   0,
532                                   kOfxParamDoubleTypeXYAbsolute);
533     gPropertySuite->propSetString(centreParamProps,
534                                   kOfxParamPropDefaultCoordinateSystem,
535                                   0,
536                                   kOfxParamCoordinatesNormalised);
537     gPropertySuite->propSetDoubleN(centreParamProps,
538                                    kOfxParamPropDefault,
539                                    2,
540                                    centreDefault);
541     gPropertySuite->propSetString(centreParamProps,
542                                   kOfxPropLabel,
543                                   0,
544                                   "Centre");
545     gPropertySuite->propSetString(centreParamProps,
546                                   kOfxParamPropHint,
547                                   0,
548                                   "The centre of the circle.");
549 
550 
551     // set the properties on the colour param
552     OfxPropertySetHandle colourParamProps;
553     static double colourDefault[] = {1.0, 1.0, 1.0, 0.5};
554 
555     gParameterSuite->paramDefine(paramSet,
556                                  kOfxParamTypeRGBA,
557                                  COLOUR_PARAM_NAME,
558                                  &colourParamProps);
559     gPropertySuite->propSetDoubleN(colourParamProps,
560                                    kOfxParamPropDefault,
561                                    4,
562                                    colourDefault);
563     gPropertySuite->propSetString(colourParamProps,
564                                   kOfxPropLabel,
565                                   0,
566                                   "Colour");
567     gPropertySuite->propSetString(centreParamProps,
568                                   kOfxParamPropHint,
569                                   0,
570                                   "The colour of the circle.");
571 
572     // and define the 'grow RoD' parameter and set its properties
573     if(gHostSupportsMultiRes) {
574       OfxPropertySetHandle growRoDParamProps;
575       gParameterSuite->paramDefine(paramSet,
576                                    kOfxParamTypeBoolean,
577                                    GROW_ROD_PARAM_NAME,
578                                    &growRoDParamProps);
579       gPropertySuite->propSetInt(growRoDParamProps,
580                                  kOfxParamPropDefault,
581                                  0,
582                                  0);
583       gPropertySuite->propSetString(growRoDParamProps,
584                                     kOfxParamPropHint,
585                                     0,
586                                     "Whether to grow the output's Region of Definition to include the circle.");
587       gPropertySuite->propSetString(growRoDParamProps,
588                                     kOfxPropLabel,
589                                     0,
590                                     "Grow RoD");
591     }
592 
593     return kOfxStatOK;
594   }
595 
596   ////////////////////////////////////////////////////////////////////////////////
597   /// instance construction
CreateInstanceAction(OfxImageEffectHandle instance)598   OfxStatus CreateInstanceAction( OfxImageEffectHandle instance)
599   {
600     OfxPropertySetHandle effectProps;
601     gImageEffectSuite->getPropertySet(instance, &effectProps);
602 
603     // To avoid continual lookup, put our handles into our instance
604     // data, those handles are guaranteed to be valid for the duration
605     // of the instance.
606     MyInstanceData *myData = new MyInstanceData;
607 
608     // Set my private instance data
609     gPropertySuite->propSetPointer(effectProps, kOfxPropInstanceData, 0, (void *) myData);
610 
611     // is this instance made for the general context?
612     const char *context = 0;
613     gPropertySuite->propGetString(effectProps, kOfxImageEffectPropContext, 0,  &context);
614     myData->isGeneralContext = context && (strcmp(context, kOfxImageEffectContextGeneral) == 0);
615 
616     // Cache the source and output clip handles
617     gImageEffectSuite->clipGetHandle(instance, "Source", &myData->sourceClip, 0);
618     gImageEffectSuite->clipGetHandle(instance, "Output", &myData->outputClip, 0);
619 
620     // Cache away the param handles
621     OfxParamSetHandle paramSet;
622     gImageEffectSuite->getParamSet(instance, &paramSet);
623     gParameterSuite->paramGetHandle(paramSet,
624                                     RADIUS_PARAM_NAME,
625                                     &myData->radiusParam,
626                                     0);
627     gParameterSuite->paramGetHandle(paramSet,
628                                     CENTRE_PARAM_NAME,
629                                     &myData->centreParam,
630                                     0);
631     gParameterSuite->paramGetHandle(paramSet,
632                                     COLOUR_PARAM_NAME,
633                                     &myData->colourParam,
634                                     0);
635 
636     if(gHostSupportsMultiRes) {
637       gParameterSuite->paramGetHandle(paramSet,
638                                       GROW_ROD_PARAM_NAME,
639                                       &myData->growRoD,
640                                       0);
641     }
642 
643     return kOfxStatOK;
644   }
645 
646   ////////////////////////////////////////////////////////////////////////////////
647   // instance destruction
DestroyInstanceAction(OfxImageEffectHandle instance)648   OfxStatus DestroyInstanceAction( OfxImageEffectHandle instance)
649   {
650     // get my instance data
651     MyInstanceData *myData = FetchInstanceData(instance);
652     delete myData;
653 
654     return kOfxStatOK;
655   }
656 
657   ////////////////////////////////////////////////////////////////////////////////
658   // clamp to 0 and MAX inclusive
659   template <class T, int MAX>
Clamp(float value)660   static inline T Clamp(float value)
661   {
662     if(MAX == 1)
663       return value; // don't clamp floating point values
664     else
665       return value < 0 ? T(0) : (value > MAX ? T(MAX) : T(value));
666   }
667 
668   ////////////////////////////////////////////////////////////////////////////////
669   // clamp to 0 and MAX inclusive
670   template <class T1, class T2>
Blend(T1 v1,T2 v2,float blend)671   static inline T1 Blend(T1 v1, T2 v2, float blend)
672   {
673     return v1 + (v2-v1) * blend;
674   }
675 
676   ////////////////////////////////////////////////////////////////////////////////
677   // iterate over our pixels and process them
678   template <class T, int MAX>
PixelProcessing(OfxImageEffectHandle instance,Image & src,Image & output,double centre[2],double radius,double colour[4],double renderScale[2],OfxRectI renderWindow)679   void PixelProcessing(OfxImageEffectHandle instance,
680                        Image &src,
681                        Image &output,
682                        double centre[2],
683                        double radius,
684                        double colour[4],
685                        double renderScale[2],
686                        OfxRectI renderWindow)
687   {
688     // pixel aspect of our output
689     float PAR = output.pixelAspectRatio();
690 
691     T colourQuantised[4];
692     for(int c = 0; c < 4; ++c) {
693       colourQuantised[c] = Clamp<T, MAX>(colour[c] * MAX);
694     }
695 
696     // now do some processing
697     for(int y = renderWindow.y1; y < renderWindow.y2; y++) {
698       if( y % 20 == 0 && gImageEffectSuite->abort(instance)) break;
699 
700       // get our y coord in canonical space
701       float yCanonical = (y + 0.5f)/renderScale[1];
702 
703       // how far are we from the centre in y, canonical
704       float dy = yCanonical - centre[1];
705 
706       // get the row start for the output image
707       T *dstPix = output.pixelAddress<T>(renderWindow.x1, y);
708 
709       for(int x = renderWindow.x1; x < renderWindow.x2; x++) {
710         // get our x pixel coord in canonical space,
711         float xCanonical = (x + 0.5) * PAR/renderScale[0];
712 
713         // how far are we from the centre in x, canonical
714         float dx = xCanonical - centre[0];
715 
716         // distance to the centre of our circle, canonical
717         float d = sqrtf(dx * dx + dy * dy);
718 
719         // this will hold the antialiased value
720         float alpha = colour[3];
721 
722         // Is the square of the distance to the centre
723         // less than the square of the radius?
724         if(d < radius) {
725           if(d > radius - 1) {
726             // we are within 1 pixel of the edge, modulate
727             // our alpha with an anti-aliasing value
728             alpha *= radius - d;
729           }
730         }
731         else {
732           // outside, so alpha is 0
733           alpha = 0;
734         }
735 
736         // get the source pixel
737         const T *srcPix = src.pixelAddressWithFallback<T>(x, y);
738 
739         // scale each component around that average
740         for(int c = 0; c < output.nComponents(); ++c) {
741           // use the mask to control how much original we should have
742           dstPix[c] = Blend(srcPix[c], colourQuantised[c], alpha);
743         }
744         dstPix += output.nComponents();
745       }
746     }
747   }
748 
749   ////////////////////////////////////////////////////////////////////////////////
750   // Render an output image
RenderAction(OfxImageEffectHandle instance,OfxPropertySetHandle inArgs,OfxPropertySetHandle)751   OfxStatus RenderAction( OfxImageEffectHandle instance,
752                           OfxPropertySetHandle inArgs,
753                           OfxPropertySetHandle /*outArgs*/)
754   {
755     // get the render window and the time from the inArgs
756     OfxTime time;
757     OfxRectI renderWindow;
758     double renderScale[2];
759     OfxStatus status = kOfxStatOK;
760 
761     gPropertySuite->propGetDouble(inArgs,
762                                   kOfxPropTime,
763                                   0,
764                                   &time);
765     gPropertySuite->propGetIntN(inArgs,
766                                 kOfxImageEffectPropRenderWindow,
767                                 4,
768                                 &renderWindow.x1);
769     gPropertySuite->propGetDoubleN(inArgs,
770                                    kOfxImageEffectPropRenderScale,
771                                    2,
772                                    renderScale);
773 
774     // get our instance data which has out clip and param handles
775     MyInstanceData *myData = FetchInstanceData(instance);
776 
777     // get our param values
778     double radius = 0.0;
779     gParameterSuite->paramGetValueAtTime(myData->radiusParam, time, &radius);
780     double centre[2];
781     gParameterSuite->paramGetValueAtTime(myData->centreParam, time, &centre[0], &centre[1]);
782     double colour[4];
783     gParameterSuite->paramGetValueAtTime(myData->colourParam, time, &colour[0], &colour[1], &colour[2], &colour[3]);
784 
785     try {
786       // fetch image to render into from that clip
787       Image outputImg(myData->outputClip, time);
788       if(!outputImg) {
789         throw " no output image!";
790       }
791 
792       // fetch image to render into from that clip
793       Image sourceImg(myData->sourceClip, time);
794       if(!sourceImg) {
795         throw " no source image!";
796       }
797 
798       // now do our render depending on the data type
799       if(outputImg.bytesPerComponent() == 1) {
800         PixelProcessing<unsigned char, 255>(instance,
801                                             sourceImg,
802                                             outputImg,
803                                             centre,
804                                             radius,
805                                             colour,
806                                             renderScale,
807                                             renderWindow);
808       }
809       else if(outputImg.bytesPerComponent() == 2) {
810         PixelProcessing<unsigned short, 65535>(instance,
811                                                sourceImg,
812                                                outputImg,
813                                                centre,
814                                                radius,
815                                                colour,
816                                                renderScale,
817                                                renderWindow);
818       }
819       else if(outputImg.bytesPerComponent() == 4) {
820         PixelProcessing<float, 1>(instance,
821                                   sourceImg,
822                                   outputImg,
823                                   centre,
824                                   radius,
825                                   colour,
826                                   renderScale,
827                                   renderWindow);
828       }
829       else {
830         throw " bad data type!";
831       }
832 
833     }
834     catch(const char *errStr ) {
835       bool isAborting = gImageEffectSuite->abort(instance);
836 
837       // if we were interrupted, the failed fetch is fine, just return kOfxStatOK
838       // otherwise, something wierd happened
839       if(!isAborting) {
840         status = kOfxStatFailed;
841       }
842       ERROR_IF(!isAborting, " Rendering failed because %s", errStr);
843     }
844     // all was well
845     return status;
846   }
847 
848   // tells the host what region we are capable of filling
849   OfxStatus
GetRegionOfDefinitionAction(OfxImageEffectHandle effect,OfxPropertySetHandle inArgs,OfxPropertySetHandle outArgs)850   GetRegionOfDefinitionAction( OfxImageEffectHandle  effect,
851                                OfxPropertySetHandle inArgs,
852                                OfxPropertySetHandle outArgs)
853   {
854     // retrieve any instance data associated with this effect
855     MyInstanceData *myData = FetchInstanceData(effect);
856 
857     OfxTime time;
858     gPropertySuite->propGetDouble(inArgs, kOfxPropTime, 0, &time);
859 
860     int growingRoD;
861     gParameterSuite->paramGetValueAtTime(myData->growRoD, time,
862                                          &growingRoD);
863 
864     // are we growing the RoD to include the circle?
865     if (!growingRoD) {
866       return kOfxStatReplyDefault;
867     }
868     else {
869       double radius = 0.0;
870       gParameterSuite->paramGetValueAtTime(myData->radiusParam, time,
871                                            &radius);
872 
873       double centre[2];
874       gParameterSuite->paramGetValueAtTime(myData->centreParam, time,
875                                            &centre[0],
876                                            &centre[1]);
877 
878       // get the source rod
879       OfxRectD rod;
880       gImageEffectSuite->clipGetRegionOfDefinition(myData->sourceClip, time, &rod);
881 
882       if(rod.x1 > centre[0] - radius) rod.x1 = centre[0] - radius;
883       if(rod.y1 > centre[1] - radius) rod.y1 = centre[1] - radius;
884 
885       if(rod.x2 < centre[0] + radius) rod.x2 = centre[0] + radius;
886       if(rod.y2 < centre[1] + radius) rod.y2 = centre[1] + radius;
887 
888       // set the rod in the out args
889       gPropertySuite->propSetDoubleN(outArgs, kOfxImageEffectPropRegionOfDefinition, 4, &rod.x1);
890 
891       // and say we trapped the action and we are at the identity
892       return kOfxStatOK;
893     }
894   }
895 
896   // are the settings of the effect making it redundant and so not do anything to the image data
IsIdentityAction(OfxImageEffectHandle instance,OfxPropertySetHandle inArgs,OfxPropertySetHandle outArgs)897   OfxStatus IsIdentityAction( OfxImageEffectHandle instance,
898                               OfxPropertySetHandle inArgs,
899                               OfxPropertySetHandle outArgs)
900   {
901     MyInstanceData *myData = FetchInstanceData(instance);
902 
903     bool isIdentity = false;
904 
905     double time;
906     gPropertySuite->propGetDouble(inArgs, kOfxPropTime, 0, &time);
907 
908     double radius = 0.0;
909     gParameterSuite->paramGetValueAtTime(myData->radiusParam, time, &radius);
910 
911     // if the radius is zero then we don't draw anything and it has no effect
912     isIdentity = radius < 0.0001;
913 
914     int growingRoD = 0;
915     if(gHostSupportsMultiRes) {
916       gParameterSuite->paramGetValueAtTime(myData->growRoD, time, &growingRoD);
917     }
918 
919     // if we are drawing out side of the RoD and we aren't growing to include it, we have no effect
920     if(!isIdentity && !growingRoD) {
921       OfxRectD bounds;
922       double centre[2];
923       gParameterSuite->paramGetValueAtTime(myData->centreParam, time, &centre[0], &centre[1]);
924 
925       gImageEffectSuite->clipGetRegionOfDefinition(myData->sourceClip, time, &bounds);
926 
927       isIdentity = (centre[0] + radius < bounds.x1 ||
928                     centre[0] - radius > bounds.x2 ||
929                     centre[1] + radius < bounds.y1 ||
930                     centre[1] - radius > bounds.y2);
931     }
932 
933 
934     if(isIdentity) {
935       // we set the name of the input clip to pull default images from
936       gPropertySuite->propSetString(outArgs, kOfxPropName, 0, "Source");
937 
938       // and say we trapped the action and we are at the identity
939       return kOfxStatOK;
940     }
941 
942     // say we aren't at the identity
943     return kOfxStatReplyDefault;
944   }
945 
946   ////////////////////////////////////////////////////////////////////////////////
947   // The main entry point function, the host calls this to get the plugin to do things.
MainEntryPoint(const char * action,const void * handle,OfxPropertySetHandle inArgs,OfxPropertySetHandle outArgs)948   OfxStatus MainEntryPoint(const char *action, const void *handle, OfxPropertySetHandle inArgs,  OfxPropertySetHandle outArgs)
949   {
950     MESSAGE(": START action is : %s \n", action );
951     // cast to appropriate type
952     OfxImageEffectHandle effect = (OfxImageEffectHandle) handle;
953 
954     OfxStatus returnStatus = kOfxStatReplyDefault;
955 
956     if(strcmp(action, kOfxActionLoad) == 0) {
957       // The very first action called on a plugin.
958       returnStatus = LoadAction();
959     }
960     else if(strcmp(action, kOfxActionDescribe) == 0) {
961       // the first action called to describe what the plugin does
962       returnStatus = DescribeAction(effect);
963     }
964     else if(strcmp(action, kOfxImageEffectActionDescribeInContext) == 0) {
965       // the second action called to describe what the plugin does in a specific context
966       returnStatus = DescribeInContextAction(effect, inArgs);
967     }
968     else if(strcmp(action, kOfxActionCreateInstance) == 0) {
969       // the action called when an instance of a plugin is created
970       returnStatus = CreateInstanceAction(effect);
971     }
972     else if(strcmp(action, kOfxActionDestroyInstance) == 0) {
973       // the action called when an instance of a plugin is destroyed
974       returnStatus = DestroyInstanceAction(effect);
975     }
976     else if(strcmp(action, kOfxImageEffectActionIsIdentity) == 0) {
977       // Check to see if our param settings cause nothing to happen
978       returnStatus = IsIdentityAction(effect, inArgs, outArgs);
979     }
980     else if(strcmp(action, kOfxImageEffectActionRender) == 0) {
981       // action called to render a frame
982       returnStatus = RenderAction(effect, inArgs, outArgs);
983     }
984     else if(gHostSupportsMultiRes && strcmp(action, kOfxImageEffectActionGetRegionOfDefinition) == 0) {
985       returnStatus = GetRegionOfDefinitionAction(effect, inArgs, outArgs);
986     }
987 
988 
989     MESSAGE(": END action is : %s \n", action );
990     /// other actions to take the default value
991     return returnStatus;
992   }
993 
994   ////////////////////////////////////////////////////////////////////////////////
995   // Call back passed to the host in the OfxPlugin struct to set our host pointer
996   //
997   // This must be called AFTER both OfxGetNumberOfPlugins and OfxGetPlugin, but
998   // before the pluginMain entry point is ever touched.
SetHostFunc(OfxHost * hostStruct)999   void SetHostFunc(OfxHost *hostStruct)
1000   {
1001     gHost = hostStruct;
1002   }
1003 
1004 } // end of anonymous namespace
1005 
1006 
1007 ////////////////////////////////////////////////////////////////////////////////
1008 // The plugin struct passed back to the host application to initiate bootstrapping\
1009 // of plugin communications
1010 static OfxPlugin effectPluginStruct =
1011 {
1012   kOfxImageEffectPluginApi,                  // The API this plugin satisfies.
1013   1,                                         // The version of the API it satisifes.
1014   "org.openeffects:CircleExamplePlugin",     // The unique ID of this plugin.
1015   1,                                         // The major version number of this plugin.
1016   0,                                         // The minor version number of this plugin.
1017   SetHostFunc,                               // Function used to pass back to the plugin the OFXHost struct.
1018   MainEntryPoint                             // The main entry point to the plugin where all actions are passed to.
1019 };
1020 
1021 ////////////////////////////////////////////////////////////////////////////////
1022 // The first of the two functions that a host application will look for
1023 // after loading the binary, this function returns the number of plugins within
1024 // this binary.
1025 //
1026 // This will be the first function called by the host.
OfxGetNumberOfPlugins(void)1027 EXPORT int OfxGetNumberOfPlugins(void)
1028 {
1029   return 1;
1030 }
1031 
1032 ////////////////////////////////////////////////////////////////////////////////
1033 // The second of the two functions that a host application will look for
1034 // after loading the binary, this function returns the 'nth' plugin declared in
1035 // this binary.
1036 //
1037 // This will be called multiple times by the host, once for each plugin present.
OfxGetPlugin(int nth)1038 EXPORT OfxPlugin * OfxGetPlugin(int nth)
1039 {
1040   if(nth == 0)
1041     return &effectPluginStruct;
1042   return 0;
1043 }
1044