1.. _invertExample: 2 3This guide will take you through the fundamentals of processing images 4in OFX. An example plugin will be used to illustrate how it all works 5and its source can be found in the C++ file 6`invert.cpp <https://github.com/ofxa/openfx/blob/master/Guide/Code/Example2/invert.cpp>`_. 7This plugin takes an image and 8inverts it (or rather calculates the complement of each component). 9Ideally you should have read the guide to the :ref:`basic machinery of an OFX 10plugin <basicExample>` before you read this guide. 11 12Action Stations! 13================ 14 15The invert example is pretty much the most minimal OFX plugin you can 16write that processes images. It leaves many things at their default 17settings which means it doesn’t have to trap more than four actions in 18total [1]_ and set very few switches. 19 20From the source, here is the main entry routine that traps those 21actions... 22 23`invert.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example2/invert.cpp#L416>`_ 24 25.. code:: c++ 26 27 //////////////////////////////////////////////////////////////////////////////// 28 // The main entry point function, the host calls this to get the plugin to do things. 29 OfxStatus MainEntryPoint(const char *action, 30 const void *handle, 31 OfxPropertySetHandle inArgs, 32 OfxPropertySetHandle outArgs) 33 { 34 // cast to appropriate type 35 OfxImageEffectHandle effect = (OfxImageEffectHandle) handle; 36 37 OfxStatus returnStatus = kOfxStatReplyDefault; 38 39 if(strcmp(action, kOfxActionLoad) == 0) { 40 // The very first action called on a plugin. 41 returnStatus = LoadAction(); 42 } 43 else if(strcmp(action, kOfxActionDescribe) == 0) { 44 // the first action called to describe what the plugin does 45 returnStatus = DescribeAction(effect); 46 } 47 else if(strcmp(action, kOfxImageEffectActionDescribeInContext) == 0) { 48 // the second action called to describe what the plugin does 49 returnStatus = DescribeInContextAction(effect, inArgs); 50 } 51 else if(strcmp(action, kOfxImageEffectActionRender) == 0) { 52 // action called to render a frame 53 returnStatus = RenderAction(effect, inArgs, outArgs); 54 } 55 56 /// other actions to take the default value 57 return returnStatus; 58 } 59 60 } // end of anonymous namespace 61 62It leaves out some of the actions we had in the last example, which were 63there for only illustrative purposes only. However, it is now trapping 64one more, the :c:macro:`kOfxImageEffectActionRender` action. Funnily 65enough, that is the action called to render a frame of output. 66 67Describing Our Plugin 68===================== 69 70We have the standard two step description process for this plugin. 71 72`invert.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example2/invert.cpp#L117>`_ 73 74.. code:: c++ 75 76 //////////////////////////////////////////////////////////////////////////////// 77 // the plugin's basic description routine 78 OfxStatus DescribeAction(OfxImageEffectHandle descriptor) 79 { 80 // set some labels and the group it belongs to 81 gPropertySuite->propSetString(effectProps, 82 kOfxPropLabel, 83 0, 84 "OFX Invert Example"); 85 gPropertySuite->propSetString(effectProps, 86 kOfxImageEffectPluginPropGrouping, 87 0, 88 "OFX Example"); 89 90 // define the image effects contexts we can be used in, in this case a simple filter 91 gPropertySuite->propSetString(effectProps, 92 kOfxImageEffectPropSupportedContexts, 93 0, 94 kOfxImageEffectContextFilter); 95 96 // set the bit depths the plugin can handle 97 gPropertySuite->propSetString(effectProps, 98 kOfxImageEffectPropSupportedPixelDepths, 99 0, 100 kOfxBitDepthFloat); 101 gPropertySuite->propSetString(effectProps, 102 kOfxImageEffectPropSupportedPixelDepths, 103 1, 104 kOfxBitDepthShort); 105 gPropertySuite->propSetString(effectProps, 106 kOfxImageEffectPropSupportedPixelDepths, 107 2, 108 kOfxBitDepthByte); 109 110 // get the property set handle for the plugin 111 OfxPropertySetHandle effectProps; 112 gImageEffectSuite->getPropertySet(descriptor, &effectProps); 113 114 // say that a single instance of this plugin can be rendered in multiple threads 115 gPropertySuite->propSetString(effectProps, 116 kOfxImageEffectPluginRenderThreadSafety, 117 0, 118 kOfxImageEffectRenderFullySafe); 119 120 // say that the host should manage SMP threading over a single frame 121 gPropertySuite->propSetInt(effectProps, 122 kOfxImageEffectPluginPropHostFrameThreading, 123 0, 124 1); 125 126 return kOfxStatOK; 127 } 128 129The function called for the describe action sets all the properties on 130an effect that are independent of specific contexts. In this case it 131sets some labels and says what contexts it can be used in, which is only 132the **filter** context, where an effect has a single input and output. 133It also says what data types it can support when processing images. This 134is a property that belongs to the plugin as a whole, not to individual 135clips (see below). If a plugin doesn’t support a data type needed by the 136host, the host is at liberty to ignore it and get on with it’s life. 137 138We said our plugin supports all the three standard pixel data types, 139which various properties throughout the API use. The values are: 140 141 142* :c:macro:`kOfxBitDepthByte` Each component will be an 8 bit unsigned integer with a maximum value of 255. 143 144* :c:macro:`kOfxBitDepthShort` Each component will be an 16 bit unsigned integer with a maximum value of 65535. 145 146* :c:macro:`kOfxBitDepthFloat` Each component will be a 32 bit floating point number with a nominal white point of 1. 147 148 149.. note:: 150 151 The :cpp:type:`OfxImageEffectHandle` passed to the describe calls should not 152 be cached away, It only represents some object used while describing 153 the effect. It is *not* the effect itself and when instances are 154 created the handle will refer to a different object entirely. In 155 general, never hang onto any effect handles in any global state. 156 157Finally our plugin is setting some flags to do with multithreaded 158rendering. The first flag, :c:macro:`kOfxImageEffectPluginRenderThreadSafety` 159is used to indicate how plugins and instances should be used when 160rendering in multiple threads. We are setting it to 161:c:macro:`kOfxImageEffectRenderFullySafe`, which means that the host can have 162any number of instances rendering and each instance could have possibly 163have simultaneous renders called on it. (eg: at separate frames). The 164other options are listed in the programming reference. 165 166The second call sets the 167:c:macro:`kOfxImageEffectPluginPropHostFrameThreading`, which says that the 168host should manage any symmetric multiprocessing when rendering the 169effect. Typically done by calling render on different tiles of the 170output image. If not set, it is up to the plugin to launch the 171appropriate number of threads and divide the processing appropriately 172across them. 173 174`invert.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example2/invert.cpp#L171>`_ 175 176.. code:: c++ 177 178 //////////////////////////////////////////////////////////////////////////////// 179 // describe the plugin in context 180 OfxStatus 181 DescribeInContextAction(OfxImageEffectHandle descriptor, 182 OfxPropertySetHandle inArgs) 183 { 184 OfxPropertySetHandle props; 185 // define the mandated single output clip 186 gImageEffectSuite->clipDefine(descriptor, "Output", &props); 187 188 // set the component types we can handle on out output 189 gPropertySuite->propSetString(props, 190 kOfxImageEffectPropSupportedComponents, 191 0, 192 kOfxImageComponentRGBA); 193 gPropertySuite->propSetString(props, 194 kOfxImageEffectPropSupportedComponents, 195 1, 196 kOfxImageComponentAlpha); 197 gPropertySuite->propSetString(props, 198 kOfxImageEffectPropSupportedComponents, 199 2, 200 kOfxImageComponentRGB); 201 202 // define the mandated single source clip 203 gImageEffectSuite->clipDefine(descriptor, "Source", &props); 204 205 // set the component types we can handle on our main input 206 gPropertySuite->propSetString(props, 207 kOfxImageEffectPropSupportedComponents, 208 0, 209 kOfxImageComponentRGBA); 210 gPropertySuite->propSetString(props, 211 kOfxImageEffectPropSupportedComponents, 212 1, 213 kOfxImageComponentAlpha); 214 gPropertySuite->propSetString(props, 215 kOfxImageEffectPropSupportedComponents, 216 2, 217 kOfxImageComponentRGB); 218 219 return kOfxStatOK; 220 } 221 222Here we are describing the plugin when it is being used as a filter. In 223this case we are describing two clips, the mandated *Source* and 224*Output* clips. Each clip has a variety of properties on them, in this 225case we are only setting what pixel components we accept on those 226inputs. The components supported (unlike the data type) is a per clip 227thinumgy. Pixels in OFX can currently only be of three types, which are 228listed below. 229 230 231:c:macro:`kOfxImageComponentRGBA` Each pixel has four samples, corresponding to Red, Green, Blue and Alpha. Packed as RGBA 232 233:c:macro:`kOfxImageComponentRGB` Each pixel has three samples, corresponding to Red, Green and Blue. Packed as RGB. 234 235:c:macro:`kOfxImageComponentAlpha` Each pixel has one sample, generally interpretted as an Alpha value. 236 237.. note:: 238 239 The OpenGL rendering extension has significantly different set of 240 capabilities for this. 241 242.. _clips: 243 244Clips 245===== 246 247I hear you ask "What are these clips of which you speak Mr Nicoletti?", 248well they are a sequence of images that vary over time. They are 249represented in the API by an :cpp:type:`OfxImageClipHandle` and have a name 250plus an associated property set. 251 252Depending on the context, you will have to describe some mandated number 253of clips with specific names. For example the filter effect has two and 254only two clips you must describe *Source* and *Output*, a **transition** 255effect has three and only three clips *SourceFrom*, *SourceTo* and 256*Output* while a **general** effect has to have one clip called *Output* 257but as many other input clips as we want. There are ``**#defines**`` for 258these in the various OFX header files. The Programming Reference has 259more information on other contexts, and we will use more in later 260examples. 261 262There are many properties on a clip, and during description you get to 263set a whole raft of them as to how you want them to behave. We are 264relying on the defaults in this example that allow us to avoid issues 265like field rendering and more. 266 267You fetch images out of clips with a function call in the image effect 268suite, where you ask for an image at a specific frame. In all cases the 269clip named "Output" is the one that will give you the images you will be 270writing to, the other clips are always sources and you should not modify 271the data in them. 272 273.. _images: 274 275Images In OFX 276============= 277 278Before I start talking over the rendering in the example plugin, I 279should tell you about images in OFX. 280 281 282Images and the Image Plane 283-------------------------- 284 285Images are contiguous rectangular regions of a nominally infinite 2D 286image plane for which the host has data samples, in the form of 287`pixels <http://alvyray.com/Memos/CG/Microsoft/6_pixel.pdf>`_. 288 289.. figure:: Pics/imagePlane.jpg 290 :scale: 50 % 291 :align: center 292 :alt: An image on the infinite image plane 293 294The figure above shows our image spanning the plane from coordinates X1 295to X2 in the X dimension and Y1 to Y2 in the Y dimension. We call these 296four numbers the image’s **bounds**, and is the region an image is 297guaranteed to have addressable data for. 298 299.. note:: 300 301 Y goes **up** in OFX land, not down as is common in desktop 302 publishing. 303 304.. note:: 305 306 That the image bound is open on the right, so iteration is 307 ``for (int x = x1; x < x2; ++x)``. This means the number of pixels 308 in the X dimension is given by X2-X1, similarly for the Y dimension. 309 310Image Data 311---------- 312 313Images are made up of chunk of memory which is interpreted to be a 2D 314array of pixels. Each pixel in an image has exactly the same number of 315**components**, each component being of exactly the same **data type**. 316OFX currently has pixels with one (A), three (RGB) or four components 317(RGBA), which can be bytes, shorts, or a 32 bit floats. 318 319.. figure:: Pics/dataLayout.jpg 320 :scale: 50 % 321 :align: center 322 :alt: Image Data Layout 323 324 325The figure above shows a small (3x4) image containing RGBA pixels. OFX 326returns a ``void *`` data pointer to the first component of the bottom 327left pixel in the image, which will be at (X1, Y1) on the image plane. 328Memory addresses increase left to right across the row of an OFX image, 329with all components and pixels hard packed and contiguous within that 330row. 331 332Rows may or may not be contiguous in memory, so in our example the 333address of component **R** at row 1 column 0, may or may not come 334directly after component **A** at (2, 0). To manage this we use "row 335bytes", which are the byte offset between rows, (**not** pixel or 336component offsets). By breaking this offset out, hosts can more easily 337map their pixel data into OFX images without having to copy. For example 338a host that natively runs with Y down and packs images with the top row 339first in memory would use negative row bytes and have the data pointer 340point to it’s last row (which is the bottom row). 341 342Pixel Address Calculation 343------------------------- 344 345So, given a coordinate on the image plane how do you calculate the 346address of a pixel in the image? Well you use the following information: 347 348- a ``void*`` pointer to the bottom left corner of the image 349 350- four integers that define the **bounds** of the image for which there 351 is data 352 353- the data type of each component 354 355- the type of each pixel (which yields the number of components per 356 pixel) 357 358- the number of bytes that is the offset between rows 359 360The code snippet below shows you how to use all that to find the address 361of a pixel whose coordinates are on the image plane. 362 363`invert.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example2/invert.cpp#L216>`_ 364 365.. code:: c++ 366 367 // Look up a pixel in the image. returns null if the pixel was not 368 // in the bounds of the image 369 template <class T> 370 static inline T * pixelAddress(int x, int y, 371 void *baseAddress, 372 OfxRectI bounds, 373 int rowBytes, 374 int nCompsPerPixel) 375 { 376 // Inside the bounds of this image? 377 if(x < bounds.x1 || x >= bounds.x2 || y < bounds.y1 || y >= bounds.y2) 378 return NULL; 379 380 // turn image plane coordinates into offsets from the bottom left 381 int yOffset = y - bounds.y1; 382 int xOffset = x - bounds.x1; 383 384 // Find the start of our row, using byte arithmetic 385 void *rowStartAsVoid = reinterpret_cast<char *>(baseAddress) + yOffset * rowBytes; 386 387 // turn the row start into a pointer to our data type 388 T *rowStart = reinterpret_cast<T *>(rowStartAsVoid); 389 390 // finally find the position of the first component of column 391 return rowStart + (xOffset * nCompsPerPixel); 392 } 393 394You will notice it is a templated function, where **T** will be 395instantiated with the appropriate component type by other code. 396**T** will be one of ``unsigned char``, ``unsigned short`` 397or ``float``. 398 399In order the function… 400 401- checks if the pixel coordinate is within the bounds of the image. If 402 it is not then we have no addressable pixel data at the point, so the 403 function gives up and return NULL as an indication of that, 404 405- as we have ``x`` and ``y`` as coordinates on the *image 406 plane*, it then turn the coordinates into offsets from the bottom 407 left of the image with a simple subtraction, 408 409- it then finds the start of the row we are interested in by scaling 410 our local y offset by ``rowBytes`` to figure the offset from our 411 base address data pointer, *in bytes*. It adds that to the base 412 address and now has the start of our row. 413 414- it turns the raw address at the start of the row into a pointer of 415 our data type, 416 417- finally it offsets to the correct column by skippying over *xLocal* 418 number of pixels, each of each which contain ``nComponents``. 419 420 421Images Are Property Sets 422------------------------ 423 424Images are property sets, you access all the data needed via the 425standard OFX property mechanism. This has allowed us to expand the 426information in an image and be 100% backwards compatible to existing 427hosts and plugins. 428 429Anyway, here is code from our example using the property mechanism to 430get the required data from an image… 431 432`invert.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example2/invert.cpp#L242>`_ 433 434.. code:: c++ 435 436 template <class T, int MAX> 437 void PixelProcessing(OfxImageEffectHandle instance, 438 OfxPropertySetHandle sourceImg, 439 OfxPropertySetHandle outputImg, 440 OfxRectI renderWindow, 441 int nComps) 442 { 443 ... 444 // fetch output image info from the property handle 445 int dstRowBytes; 446 OfxRectI dstBounds; 447 void *dstPtr = NULL; 448 gPropertySuite->propGetInt(outputImg, kOfxImagePropRowBytes, 0, &dstRowBytes); 449 gPropertySuite->propGetIntN(outputImg, kOfxImagePropBounds, 4, &dstBounds.x1); 450 gPropertySuite->propGetPointer(outputImg, kOfxImagePropData, 0, &dstPtr); 451 452 ... 453 } 454 455 456 OfxStatus RenderAction( OfxImageEffectHandle instance, 457 OfxPropertySetHandle inArgs, 458 OfxPropertySetHandle outArgs) 459 { 460 ... 461 // figure out the component type 462 char *cstr; 463 gPropertySuite->propGetString(outputImg, kOfxImageEffectPropComponents, 0, &cstr); 464 std::string components = cstr; 465 466 ... 467 // figure out the data types 468 gPropertySuite->propGetString(outputImg, kOfxImageEffectPropPixelDepth, 0, &cstr); 469 std::string dataType = cstr; 470 ... 471 } 472 473There are many more properties in an image, but we won’t need them for 474this simple example and they’ll be covered in other tutorials. 475 476.. _the_render_action: 477 478The Render Action 479================= 480 481As stated above, the render action is the one used to get a plugin to 482actually process images. I’ll go through it in stages rather than have 483one big listing. 484 485`invert.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example2/invert.cpp#L310>`_ 486 487.. code:: c++ 488 489 //////////////////////////////////////////////////////////////////////////////// 490 // Render an output image 491 OfxStatus RenderAction( OfxImageEffectHandle instance, 492 OfxPropertySetHandle inArgs, 493 OfxPropertySetHandle outArgs) 494 { 495 // get the render window and the time from the inArgs 496 OfxTime time; 497 OfxRectI renderWindow; 498 OfxStatus status = kOfxStatOK; 499 500 gPropertySuite->propGetDouble(inArgs, kOfxPropTime, 0, &time); 501 gPropertySuite->propGetIntN(inArgs, kOfxImageEffectPropRenderWindow, 4, &renderWindow.x1); 502 503This first listing shows how the **inArgs** are being used to say what 504exactly to render. The property :c:macro:`kOfxPropTime` on **inArgs** is 505the frame of the output clip to render. The property 506:c:macro:`kOfxImageEffectPropRenderWindow` is the region that should be 507written to. 508 509The output image (which will be fetched later on) will have a **bounds** 510that are at least as big as the render window. The bounds of the output 511image could infact be larger. This could happen if a host is 512simultaneously calling the render action in separate threads to perform 513symmetric multi-processing, each thread would be given a different 514render window to fill in of the larger output image. 515 516.. note:: 517 518 A plugin can have multiple actions being simultaneously in separate 519 threads, especially the render action. Do not rely on any local 520 state if you can help it. You can control how threading works in the 521 describe actions. 522 523.. note:: 524 525 To allow a plugin to be called in an SMP manner, or have multiple 526 instances simultaneously rendering, the API has been designed so 527 that the plugin does not rely on any implicit state, such as time, 528 everything is explicit. 529 530`invert.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example2/invert.cpp#L323>`_ 531 532.. code:: c++ 533 534 // fetch output clip 535 OfxImageClipHandle outputClip; 536 gImageEffectSuite->clipGetHandle(instance, "Output", &outputClip, NULL); 537 538 // fetch main input clip 539 OfxImageClipHandle sourceClip; 540 gImageEffectSuite->clipGetHandle(instance, "Source", &sourceClip, NULL); 541 542This next snippet fetches two clip handles by name from the instance, 543using the image effect suite. [2]_ 544 545`invert.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example2/invert.cpp#L331>`_ 546 547.. code:: c++ 548 549 // the property sets holding our images 550 OfxPropertySetHandle outputImg = NULL, sourceImg = NULL; 551 try { 552 // fetch image to render into from that clip 553 OfxPropertySetHandle outputImg; 554 if(gImageEffectSuite->clipGetImage(outputClip, time, NULL, &outputImg) != kOfxStatOK) { 555 throw " no output image!"; 556 } 557 558 // fetch image at render time from that clip 559 if (gImageEffectSuite->clipGetImage(sourceClip, time, NULL, &sourceImg) != kOfxStatOK) { 560 throw " no source image!"; 561 } 562 563We now (inside a try/catch block) fetch two images from the clips, again 564using the image effect suite. Note we are asking for images at the frame 565we were told to render. Effects that need images from other frames can 566pass in different values to :cpp:func:`OfxImageEffectSuiteV1::clipGetImage`, but will need to trap 567more actions than we have to make that all work correctly. 568 569We will be given back two property set handles which represent our 570images. If the call failed (which could be for a variety of good 571reasons) we give up with a ``throw``. 572 573`invert.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example2/invert.cpp#L345>`_ 574 575.. code:: c++ 576 577 // figure out the data types 578 char *cstr; 579 gPropertySuite->propGetString(outputImg, kOfxImageEffectPropComponents, 0, &cstr); 580 std::string components = cstr; 581 582 // how many components per pixel? 583 int nComps = 0; 584 if(components == kOfxImageComponentRGBA) { 585 nComps = 4; 586 } 587 else if(components == kOfxImageComponentRGB) { 588 nComps = 3; 589 } 590 else if(components == kOfxImageComponentAlpha) { 591 nComps = 1; 592 } 593 else { 594 throw " bad pixel type!"; 595 } 596 597Now we want to know what’s inside our image’s pixels, so we can 598correctly process it. We ask what components are present in the output 599image. Because we have left certain settings at the default, the source 600and output images will always have the same number of components and the 601same data types. Which is why we aren’t checking for the source for its 602pixel information. 603 604`invert.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example2/invert.cpp#L365>`_ 605 606.. code:: c++ 607 608 // now do our render depending on the data type 609 gPropertySuite->propGetString(outputImg, kOfxImageEffectPropPixelDepth, 0, &cstr); 610 std::string dataType = cstr; 611 612 if(dataType == kOfxBitDepthByte) { 613 PixelProcessing<unsigned char, 255>(instance, sourceImg, outputImg, renderWindow, nComps); 614 } 615 else if(dataType == kOfxBitDepthShort) { 616 PixelProcessing<unsigned short, 65535>(instance, sourceImg, outputImg, renderWindow, nComps); 617 } 618 else if (dataType == kOfxBitDepthFloat) { 619 PixelProcessing<float, 1>(instance, sourceImg, outputImg, renderWindow, nComps); 620 } 621 else { 622 throw " bad data type!"; 623 throw 1; 624 } 625 626Now we are enquiring as to what C type the components our image will be. 627Again throwing if something has gone wrong. We use the data type to 628correctly instantiate our templated function which will do the grunt 629work of iterating over pixels. Note also that it is passing the nominal 630maximum value of the data type as a template argument. 631 632`invert.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example2/invert.cpp#L383>`_ 633 634.. code:: c++ 635 636 } 637 catch(const char *errStr ) { 638 bool isAborting = gImageEffectSuite->abort(instance); 639 640 // if we were interrupted, the failed fetch is fine, just return kOfxStatOK 641 // otherwise, something weird happened 642 if(!isAborting) { 643 status = kOfxStatFailed; 644 } 645 ERROR_IF(!isAborting, " Rendering failed because %s", errStr); 646 647 } 648 649 if(sourceImg) 650 gImageEffectSuite->clipReleaseImage(sourceImg); 651 if(outputImg) 652 gImageEffectSuite->clipReleaseImage(outputImg); 653 654 // all was well 655 return status; 656 } 657 658This last bit is basically clean up. We have the ``catch`` for our 659try/catch block. The first thing it does is ask the host application is 660the effect being told to stop by calling the :cpp:func:`OfxImageEffectSuiteV1::abort` function on 661the effect suite. We might have ended up in the catch block because the 662an image could not be fetched, if that was a side effect of the host 663interrupting processing, it is *not* counted as an error. So we check 664that before we return a failed error state from our action. 665 666Finally we release the images we have fetched and return the error 667status. 668 669.. note:: 670 671 Images should not be held onto outside the scope of the action they 672 were fetched in, the data will not be guaranteed to be valid. It is 673 polite to release them as soon as possible, especially if you are 674 fetching multiple images on input. 675 676Now for our pixel pushing code. [3]_ 677 678`invert.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example2/invert.cpp#L242>`_ 679 680.. code:: c++ 681 682 // iterate over our pixels and process them 683 template <class T, int MAX> 684 void PixelProcessing(OfxImageEffectHandle instance, 685 OfxPropertySetHandle sourceImg, 686 OfxPropertySetHandle outputImg, 687 OfxRectI renderWindow, 688 int nComps) 689 { 690 // fetch output image info from the property handle 691 int dstRowBytes; 692 OfxRectI dstBounds; 693 void *dstPtr = NULL; 694 gPropertySuite->propGetInt(outputImg, kOfxImagePropRowBytes, 0, &dstRowBytes); 695 gPropertySuite->propGetIntN(outputImg, kOfxImagePropBounds, 4, &dstBounds.x1); 696 gPropertySuite->propGetPointer(outputImg, kOfxImagePropData, 0, &dstPtr); 697 698 if(dstPtr == NULL) { 699 throw "Bad destination pointer"; 700 } 701 702 // fetch input image info from the property handle 703 int srcRowBytes; 704 OfxRectI srcBounds; 705 void *srcPtr = NULL; 706 gPropertySuite->propGetInt(sourceImg, kOfxImagePropRowBytes, 0, &srcRowBytes); 707 gPropertySuite->propGetIntN(sourceImg, kOfxImagePropBounds, 4, &srcBounds.x1); 708 gPropertySuite->propGetPointer(sourceImg, kOfxImagePropData, 0, &srcPtr); 709 710 if(srcPtr == NULL) { 711 throw "Bad source pointer"; 712 } 713 714We’ve shown bits of this before. Here we have a templated function that 715we use to process our pixels. It is templated on the data type that the 716components in each pixel will be, as well as a nominal *max* value to 717use in our invert computation. 718 719The first thing it does is to pull out the bounds, rowbytes and 720destination pointer of our two images. We can now iterate over the 721render window and set pixels in the output image. 722 723`invert.cpp <https://github.com/ofxa/openfx/blob/doc/Documentation/sources/Guide/Code/Example2/invert.cpp#L273>`_ 724 725.. code:: c++ 726 727 // and do some inverting 728 for(int y = renderWindow.y1; y < renderWindow.y2; y++) { 729 if(y % 20 == 0 && gImageEffectSuite->abort(instance)) break; 730 731 // get the row start for the output image 732 T *dstPix = pixelAddress<T>(renderWindow.x1, y, dstPtr, dstBounds, dstRowBytes, nComps); 733 734 for(int x = renderWindow.x1; x < renderWindow.x2; x++) { 735 736 // get the source pixel 737 T *srcPix = pixelAddress<T>(x, y, srcPtr, srcBounds, srcRowBytes, nComps); 738 739 if(srcPix) { 740 // we have one, iterate each component in the pixels 741 for(int i = 0; i < nComps; ++i) { 742 if(i != 3) { // We don't invert alpha. 743 *dstPix = MAX - *srcPix; // invert 744 } 745 else { 746 *dstPix = *srcPix; 747 } 748 ++dstPix; ++srcPix; 749 } 750 } 751 else { 752 // we don't have a pixel in the source image, set output to black 753 for(int i = 0; i < nComps; ++i) { 754 *dstPix = 0; 755 ++dstPix; 756 } 757 } 758 } 759 } 760 } 761 762The first thing we do at each row we are processing is to check that the 763host hasn’t told our plugin to abort processing. (Ideally you can do 764this a bit less often than every line). We only to this every 20th row, 765as the overhead on the host side to check for an abort might be quite 766high. 767 768The next thing we do is to use the ``pixelAddress`` function to find 769the address of the first component of the first pixel in the current, 770and we put it in ``dstPix``. Because we have a guarantee that the 771bounds of the output image are at least as big as the render window, we 772can simply increment ``dstPix`` across the row as we iterate over 773the image. 774 775Now we iterate across the row. We attempt to fetch the address of the 776source pixel at our x,y location in the image plane. If we get it we 777iterate over the number of component, setting the output to be the 778invert [4]_ of the input. If we don’t get it, we set the output pixel 779to all zero. 780 781.. note:: 782 783 You notice that we are continually calculating the address of 784 ``srcPix`` at each pixel location and not incrementing the 785 pointer as we could with ``dstPix``. The reason for this is 786 that, at the default settings, there is no guarantee as to the 787 bounds of the input image. It need not be congruent with any other 788 input, the output or the render window. 789 790I could obviously write this much more efficiently and avoid the 791continual address calculation. However for illustrative purposes I 792haven’t done that. 793 794Summary 795======= 796 797This plugin has shown you the basics of working with OFX images, the 798main things it illustrated were… 799 800- what are :ref:`clips <clips>` and how we get images from clips, 801 802- how :ref:`images <images>` are laid out in memory and how to 803 access pixels, 804 805- the basics of the :ref:`render action <the_render_action>` 806 807.. [1] 808 I won’t bother going into the boot strapping boiler plate, if you are 809 interested you can look at the source directly. 810 811.. [2] 812 The **NULL** at the end could have been the address of a property set 813 handle if the effect needed to enquire about the clips properties. 814 815.. [3] 816 This is purely illustrative as to how the API works, it is in no way 817 fast code, I would be ashamed to put code like this into a serious 818 piece of image processing. 819 820.. [4] 821 complement really 822 823 824