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, ¶mSet);
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 ¢reParamProps);
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, ¶mSet);
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, ¢re[0], ¢re[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 ¢re[0],
876 ¢re[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, ¢re[0], ¢re[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