1.. _gainExample: 2 3This guide will take you through the basics of creating and using 4parameters in OFX. An example plugin will be used to illustrate how it 5all works and its source can be found in the C++ file 6`gain.cpp <https://github.com/ofxa/openfx/blob/master/Guide/Code/Example3/gain.cpp>`_. 7This plugin takes an image and 8multiplies the pixel by the value held in a user visible parameter. 9Ideally you should have read the guide to the :ref:`basic image 10processing <invertExample>` before you read this guide. 11 12.. _parameters: 13 14Parameters 15========== 16 17Host applications need parameters to make their own effects work. Such 18as the size of a blur effect, colours to apply to some text, a point for 19the centre of a lens flare and so on. The host app will use some widget 20set to present a user interface for the parameter, have ways to do 21undo/redo, saving/loading, manage animation etc… 22 23The OFX parameters suite is the bridge from a plugin to the host’s 24native parameter set. So plugin devs don’t have to do all that work for 25themselves and also get the other advantages a host’s parameters system 26may give (e.g. scripting). 27 28The way it works is fairly simple in concept, we get a plugin to tell 29the host what parameters it wants during description. When an instance 30is created, the host will make whatever native data structures it needs 31to manage those params. The plugin can then grab values from the various 32parameters to do what it needs to do during various actions, it can even 33write back to parameters under certain conditions. 34 35Our simple gain example will make two parameters, a parameter that is of 36type ``double`` which is the gain amount and a ``bool`` param 37which controls whether to apply the gain amount to the Alpha of an RGBA 38image. There are more parameter types, and quite a few properties you 39can set on a param to control how it should behave in a host 40application. 41The current parameter types available are :ref:`listed here.<parameterTypes>` 42 43.. note:: 44 45 A key concept in OFX is that the state of a plugin instance is 46 totally and uniquely defined by the value of its parameters and 47 input clips. You are asking for trouble if you try and store 48 important data in separate files and so on. The host won’t be able 49 to properly know when things have changed, how to manage that extra 50 data and so on. Attempting to manage data outside of the parameter 51 set will almost certainly cause hosts to lose track of the correct 52 render and you will get angry users. This is a fundamental aspect of 53 the API. If it isn’t in a parameter, it is going to cause problems 54 with the host if you rely on it. 55 56.. __actions: 57 58Actions 59======= 60 61This example doesn’t trap any actions you haven’t already seen in the 62other examples, it just does a little bit more in them. Seeing as you 63should be familiar with how the main entry point works, I won’t bother 64with the code listing from now on. The actions our plugin traps are now: 65 66- :c:macro:`kOfxActionLoad` - to grab suites from the host, 67 68- :c:macro:`kOfxActionDescribe` and :c:macro:`kOfxImageEffectActionDescribeInContext` - to 69 describe the plugin to the host, *including parameters*, 70 71- :c:macro:`kOfxActionCreateInstance` and :c:macro:`kOfxActionDestroyInstance` - to create 72 and destroy instance data, where we cache handles to clips and 73 parameters, 74 75- :c:macro:`kOfxImageEffectActionIsIdentity` - to check if the parameter values 76 are at their defaults and so the plugin can be ignore by the host, 77 78- :c:macro:`kOfxImageEffectActionRender` - to actually process pixels. 79 80Now seeing as we are going to be playing with parameters, our plugin 81will need a new suite, the parameters suite, and our load action now 82looks like: 83 84`gain.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example3/gain.cpp#L101>`_ 85 86.. code:: c++ 87 88 OfxPropertySuiteV1 *gPropertySuite = 0; 89 OfxImageEffectSuiteV1 *gImageEffectSuite = 0; 90 OfxParameterSuiteV1 *gParameterSuite = 0; 91 92 //////////////////////////////////////////////////////////////////////////////// 93 // get the named suite and put it in the given pointer, with error checking 94 template <class SUITE> 95 void FetchSuite(SUITE *& suite, const char *suiteName, int suiteVersion) 96 { 97 suite = (SUITE *) gHost->fetchSuite(gHost->host, suiteName, suiteVersion); 98 if(!suite) { 99 ERROR_ABORT_IF(suite == NULL, 100 "Failed to fetch %s verison %d from the host.", 101 suiteName, 102 suiteVersion); 103 } 104 } 105 106 //////////////////////////////////////////////////////////////////////////////// 107 // The first _action_ called after the binary is loaded 108 OfxStatus LoadAction(void) 109 { 110 // fetch our three suites 111 FetchSuite(gPropertySuite, kOfxPropertySuite, 1); 112 FetchSuite(gImageEffectSuite, kOfxImageEffectSuite, 1); 113 FetchSuite(gParameterSuite, kOfxParameterSuite, 1); 114 115 return kOfxStatOK; 116 } 117 118You can see I’ve written a ``FetchSuite`` function, as I got bored 119of writing the same code over and over. We are now fetching the a suite 120of type :cpp:class:`OfxParameterSuiteV1` which is defined in the header file 121**ofxParam.h**. [2]_. 122 123.. _describing_our_plugin: 124 125Describing Our Plugin 126===================== 127 128We have the standard two step description process for this plugin. The 129Describe action is almost exactly the same as in our previous examples, 130some names and labels have been changed is all, so I won’t list it. 131However, the describe in context action has a few more things going on. 132 133In the listings below I’ve chopped out the code to describe clips, as it 134is exactly the same as in the last example. What’s new is the bit where 135we describe parameters. I’ll show the describe in context action in 136several small chunks to take you through it. 137 138`gain.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example3/gain.cpp#L228>`_ 139 140.. code:: c++ 141 142 OfxStatus 143 DescribeInContextAction(OfxImageEffectHandle descriptor, 144 OfxPropertySetHandle inArgs) 145 { 146 ... 147 BIG SNIP OF EXACTLY THE SAME CODE IN THE LAST EXAMPLE 148 ... 149 150 // first get the handle to the parameter set 151 OfxParamSetHandle paramSet; 152 gImageEffectSuite->getParamSet(descriptor, ¶mSet); 153 154 // properties on our parameter 155 OfxPropertySetHandle paramProps; 156 157 // now define a 'gain' parameter and set its properties 158 gParameterSuite->paramDefine(paramSet, 159 kOfxParamTypeDouble, 160 GAIN_PARAM_NAME, 161 ¶mProps); 162 163The first thing we do is to grab a :c:type:`OfxParamSetHandle` from the 164effect descriptor. This object represents all the parameters attached to 165a plugin and is independent and orthogonal to an image effect. 166 167The parameter suite is then used to define a parameter on that parameter 168set. In this case its type is double, and its name is "gain". These are 169the two most important things for a parameter. 170 171.. note:: 172 173 The name uniquely identifies that parameter within the API, so no 174 two parameters can have the same name. 175 176The last argument to `paramDefine` is an optional pointer to the 177new parameter’s property set handle. Each parameter has a set of 178properties we use to refine its behaviour, most of which have sensible 179defaults. 180 181`gain.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example3/gain.cpp#L278>`_ 182 183.. code:: c++ 184 185 gPropertySuite->propSetString(paramProps, 186 kOfxParamPropDoubleType, 187 0, 188 kOfxParamDoubleTypeScale); 189 190The first property on our *gain* param we set is the kind of double 191parameter it is. Many host applications have different kind of double 192parameters and user interfaces that make working with them easier. For 193example a parameter used to control a rotation might have a little dial 194in the UI to spin the angle, a 2D position parameter might get cross 195hairs over the image and so on. In this case we are saying that our 196double parameter represents a scaling value. OFX has more kinds of 197double parameter which you can use to best for your effect. 198 199`gain.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example3/gain.cpp#L282>`_ 200 201.. code:: c++ 202 203 gPropertySuite->propSetDouble(paramProps, 204 kOfxParamPropDefault, 205 0, 206 1.0); 207 gPropertySuite->propSetDouble(paramProps, 208 kOfxParamPropMin, 209 0, 210 0.0); 211 212This section sets a default value for our parameter and a logical a 213minimum value below which it cannot go. Note it does not set a maximum 214value, so the parameter should not be clamped to any upper value ever. 215 216`gain.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example3/gain.cpp#L290>`_ 217 218.. code:: c++ 219 220 gPropertySuite->propSetDouble(paramProps, 221 kOfxParamPropDisplayMin, 222 0, 223 0.0); 224 gPropertySuite->propSetDouble(paramProps, 225 kOfxParamPropDisplayMax, 226 0, 227 10.0); 228 229Numbers are often manipulated with sliders widgets in user interfaces, 230and it is useful to set a range on those sliders. Which is exactly what 231we are doing here. This is distinct to the logical mimimum and maximum 232values, so you can set a *useful* range for the UI, but still allow the 233values to be outside that range. So here a slider would only allow 234values between 0.0 and 10.0 for our gain param, but the parameter could 235be set to a million via other means, eg: typing in a UI number box, 236animation, scripting whatever. 237 238`gain.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example3/gain.cpp#L298>`_ 239 240.. code:: c++ 241 242 gPropertySuite->propSetString(paramProps, 243 kOfxPropLabel, 244 0, 245 "Gain"); 246 gPropertySuite->propSetString(paramProps, 247 kOfxParamPropHint, 248 0, 249 "How much to multiply the image by."); 250 251Here we are setting two text field on the param. The first is a label 252for the parameter. This is to be used in any UI the host has to label 253the parameter. It defaults to the name of the param, but it can be 254entirely different. Finally we set a hint string to be used for the 255parameter. 256 257`gain.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example3/gain.cpp#L308>`_ 258 259.. code:: c++ 260 261 // and define the 'applyToAlpha' parameters and set its properties 262 gParameterSuite->paramDefine(paramSet, 263 kOfxParamTypeBoolean, 264 APPLY_TO_ALPHA_PARAM_NAME, 265 ¶mProps); 266 gPropertySuite->propSetInt(paramProps, 267 kOfxParamPropDefault, 268 0, 269 0); 270 gPropertySuite->propSetString(paramProps, 271 kOfxParamPropHint, 272 0, 273 "Whether to apply the gain value to alpha as well."); 274 gPropertySuite->propSetString(paramProps, 275 kOfxPropLabel, 276 0, 277 "Apply To Alpha"); 278 279 return kOfxStatOK; 280 } 281 282In this last section we define a second parameter, named *applyToAlpha*, 283which is of type boolean. We then set some obvious state on it and we 284are done. Notice the label we set, it is much clearer to read than the 285name. 286 287And that’s it, we’ve defined two parameters for our plugin. There are 288many more properties you can set on your plugin to control how they 289behave and to give hints as to what you are going to do to them. 290 291.. figure:: Pics/GainControlPanelNuke.jpg 292 :scale: 100 % 293 :align: center 294 :alt: Control Panel For Our Example In Nuke 295 296Finally, the image above shows the control panel for an instance of our 297example inside Nuke. 298 299.. _instances_and_parameters: 300 301Instances and Parameters 302======================== 303 304When the host creates an instance of the plugin, it will first create 305all the native data structures it needs to represent the plugin, fully 306populate them with the required values, and only then call the create 307instance action. 308 309So what happens in the create instance action then? Possibly nothing, 310you can always grab parameters from an instance by name at any time. But 311to make our code a bit cleaner and to show an example of instance data 312being used, we are going to trap create instance. 313 314`gain.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example3/gain.cpp#L111>`_ 315 316.. code:: c++ 317 318 //////////////////////////////////////////////////////////////////////////////// 319 // our instance data, where we are caching away clip and param handles 320 struct MyInstanceData { 321 // handles to the clips we deal with 322 OfxImageClipHandle sourceClip; 323 OfxImageClipHandle outputClip; 324 325 // handles to a our parameters 326 OfxParamHandle gainParam; 327 OfxParamHandle applyToAlphaParam; 328 }; 329 330To stop duplicating code all over, and to minimise fetches to various 331handles, we are going to cache away handles to our clips and parameters 332in a simple struct. Note that these handles are valid for the duration 333of the instance. 334 335`gain.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example3/gain.cpp#L330>`_ 336 337.. code:: c++ 338 339 //////////////////////////////////////////////////////////////////////////////// 340 /// instance construction 341 OfxStatus CreateInstanceAction( OfxImageEffectHandle instance) 342 { 343 OfxPropertySetHandle effectProps; 344 gImageEffectSuite->getPropertySet(instance, &effectProps); 345 346 // To avoid continual lookup, put our handles into our instance 347 // data, those handles are guaranteed to be valid for the duration 348 // of the instance. 349 MyInstanceData *myData = new MyInstanceData; 350 351 // Set my private instance data 352 gPropertySuite->propSetPointer(effectProps, kOfxPropInstanceData, 0, (void *) myData); 353 354 // Cache the source and output clip handles 355 gImageEffectSuite->clipGetHandle(instance, "Source", &myData->sourceClip, 0); 356 gImageEffectSuite->clipGetHandle(instance, "Output", &myData->outputClip, 0); 357 358 // Cache away the param handles 359 OfxParamSetHandle paramSet; 360 gImageEffectSuite->getParamSet(instance, ¶mSet); 361 gParameterSuite->paramGetHandle(paramSet, 362 GAIN_PARAM_NAME, 363 &myData->gainParam, 364 0); 365 gParameterSuite->paramGetHandle(paramSet, 366 APPLY_TO_ALPHA_PARAM_NAME, 367 &myData->applyToAlphaParam, 368 0); 369 370 return kOfxStatOK; 371 } 372 373So here is the function called when we trap a create instance action. 374You can see that it allocates a MyInstanceData struct and caches it away 375in the instance’s property set. 376 377It then fetches handles to the two clips and two parameters by name and 378caches those into the newly created struct. 379 380`gain.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example3/gain.cpp#L123>`_ 381 382.. code:: c++ 383 384 //////////////////////////////////////////////////////////////////////////////// 385 // get my instance data from a property set handle 386 MyInstanceData *FetchInstanceData(OfxPropertySetHandle effectProps) 387 { 388 MyInstanceData *myData = 0; 389 gPropertySuite->propGetPointer(effectProps, 390 kOfxPropInstanceData, 391 0, 392 (void **) &myData); 393 return myData; 394 } 395 396And here is a simple function to fetch instance data. It is actually 397overloaded and there is another version that take an 398:c:type:`OfxImageEffectHandle`. 399 400Of course we now need to trap the destroy instance action to delete our 401instance data, otherwise we will get memory leaks. 402 403`gain.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example3/gain.cpp#L364>`_ 404 405.. code:: c++ 406 407 //////////////////////////////////////////////////////////////////////////////// 408 // instance destruction 409 OfxStatus DestroyInstanceAction( OfxImageEffectHandle instance) 410 { 411 // get my instance data 412 MyInstanceData *myData = FetchInstanceData(instance); 413 delete myData; 414 415 return kOfxStatOK; 416 } 417 418.. _getting_values_from_instances: 419 420Getting Values From Instances 421============================= 422 423So we’ve define our parameters, we’ve got handles to the instance of 424them, but we will want to grab the value of the parameters to actually 425use them at render time. 426 427`gain.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example3/gain.cpp#L490>`_ 428 429.. code:: c++ 430 431 //////////////////////////////////////////////////////////////////////////////// 432 // Render an output image 433 OfxStatus RenderAction( OfxImageEffectHandle instance, 434 OfxPropertySetHandle inArgs, 435 OfxPropertySetHandle outArgs) 436 { 437 // get the render window and the time from the inArgs 438 OfxTime time; 439 OfxRectI renderWindow; 440 OfxStatus status = kOfxStatOK; 441 442 gPropertySuite->propGetDouble(inArgs, kOfxPropTime, 0, &time); 443 gPropertySuite->propGetIntN(inArgs, kOfxImageEffectPropRenderWindow, 4, &renderWindow.x1); 444 445 // get our instance data which has out clip and param handles 446 MyInstanceData *myData = FetchInstanceData(instance); 447 448 // get our param values 449 double gain = 1.0; 450 int applyToAlpha = 0; 451 gParameterSuite->paramGetValueAtTime(myData->gainParam, time, &gain); 452 gParameterSuite->paramGetValueAtTime(myData->applyToAlphaParam, time, &applyToAlpha); 453 454 .... 455 456We are using the :cpp:func:`OfxParameterSuiteV1::paramGetValueAtTime` suite function to get the 457value of our parameters for the given time we are rendering at. Nearly 458all actions passed to an instance will have a time to perform the 459instance at, you should use this when fetching values out of a param. 460 461The param get value functions use var-args to return values to plugins, 462similar to a C scanf function. 463 464And finally here is a snippet of the templated pixel pushing code where 465we do the actuall processing using our parameter values; 466 467`gain.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example3/gain.cpp#L437>`_ 468 469.. code:: c++ 470 471 // and do some processing 472 for(int y = renderWindow.y1; y < renderWindow.y2; y++) { 473 if(y % 20 == 0 && gImageEffectSuite->abort(instance)) break; 474 475 // get the row start for the output image 476 T *dstPix = pixelAddress<T>(renderWindow.x1, y, 477 dstPtr, 478 dstBounds, 479 dstRowBytes, 480 nComps); 481 482 for(int x = renderWindow.x1; x < renderWindow.x2; x++) { 483 484 // get the source pixel 485 T *srcPix = pixelAddress<T>(x, y, 486 srcPtr, 487 srcBounds, 488 srcRowBytes, 489 nComps); 490 491 if(srcPix) { 492 // we have one, iterate each component in the pixels 493 for(int i = 0; i < nComps; ++i) { 494 if(i != 3 || applyToAlpha) { 495 // multiply our source component by our gain value 496 double value = *srcPix * gain; 497 498 // if it has gone out of legal bounds, clamp it 499 if(MAX != 1) { // we let floating point pixels over and underflow 500 value = value < 0 ? 0 : (value > MAX ? MAX : value); 501 } 502 *dstPix = T(value); 503 } 504 else { 505 *dstPix = *srcPix; 506 } 507 // increment to next component 508 ++dstPix; ++srcPix; 509 } 510 } 511 else { 512 // we don't have a pixel in the source image, set output to zero 513 for(int i = 0; i < nComps; ++i) { 514 *dstPix = 0; 515 ++dstPix; 516 } 517 } 518 } 519 } 520 521Notice that we are checking to see if ``MAX != 1``, which means our 522pixels are not floating point. If that is the case, we are clamping the 523pixel’s value so we don’t get integer overflow. 524 525.. _summary: 526 527Summary 528======= 529 530This plugin has shown you the basics of working with OFX parameters, the 531main things it illustrated were: 532 533- defining parameters in the define in context action, 534 535- setting properties to control the behaviour of parameters, 536 537- using the instance data pointer to cache away handles to instances of 538 parameters and clips, 539 540- fetching values of a parameter from parameter instance handles and 541 using them to process pixels. 542 543.. [1] 544 the API manages all floating point params as doubles, the host could 545 be using 32 bit floats, or fixed precision for that matter, so long 546 as the values are passed back and forth over the API as doubles, all 547 will be fine 548 549.. [2] 550 The suite is completely independent of the image effect suite and 551 could happily be used to describe parameters to other types of 552 plugins 553 554