// look up a pixel in the image, does bounds checking to see if it is in the image rectangle inline OfxRGBAColourB * pixelAddress(OfxRGBAColourB *img, OfxRectI rect, int x, int y, int bytesPerLine) { if(x < rect.x1 || x >= rect.x2 || y < rect.y1 || y > rect.y2) return 0; OfxRGBAColourB *pix = (OfxRGBAColourB *) (((char *) img) + (y - rect.y1) * bytesPerLine); pix += x - rect.x1; return pix; } // the process code that the host sees static OfxStatus render(OfxImageEffectHandle instance, OfxPropertySetHandle inArgs, OfxPropertySetHandle outArgs) { // get the render window and the time from the inArgs OfxTime time; OfxRectI renderWindow; gPropertySuite->propGetDouble(inArgs, kOfxPropTime, 0, &time); gPropertySuite->propGetIntN(inArgs, kOfxImageEffectPropRenderWindow, 4, &renderWindow.x1); // fetch output clip OfxImageClipHandle outputClip; gEffectSuite->clipGetHandle(instance, "Output", &outputClip, 0); // fetch image to render into from that clip OfxPropertySetHandle outputImg; gEffectSuite->clipGetImage(outputClip, time, NULL, &outputImg); // fetch output image info from that handle int dstRowBytes, dstBitDepth; OfxRectI dstRect; void *dstPtr; gPropertySuite->propGetInt(outputImg, kOfxImagePropRowBytes, 0, &dstRowBytes); gPropertySuite->propGetIntN(outputImg, kOfxImagePropBounds, 4, &dstRect.x1); gPropertySuite->propGetPointer(outputImg, kOfxImagePropData, 0, &dstPtr); // fetch main input clip OfxImageClipHandle sourceClip; gEffectSuite->clipGetHandle(instance, "Source", &sourceClip, 0); // fetch image at render time from that clip OfxPropertySetHandle sourceImg; gEffectSuite->clipGetImage(sourceClip, time, NULL, &sourceImg); // fetch image info out of that handle int srcRowBytes, srcBitDepth; OfxRectI srcRect; void *srcPtr; gPropertySuite->propGetInt(sourceImg, kOfxImagePropRowBytes, 0, &srcRowBytes); gPropertySuite->propGetIntN(sourceImg, kOfxImagePropBounds, 4, &srcRect.x1); gPropertySuite->propGetPointer(sourceImg, kOfxImagePropData, 0, &srcPtr); // cast data pointers to 8 bit RGBA OfxRGBAColourB *src = (OfxRGBAColourB *) srcPtr; OfxRGBAColourB *dst = (OfxRGBAColourB *) dstPtr; // and do some inverting for(int y = renderWindow.y1; y < renderWindow.y2; y++) { if(gEffectSuite->abort(instance)) break; OfxRGBAColourB *dstPix = pixelAddress(dst, dstRect, renderWindow.x1, y, dstRowBytes); for(int x = renderWindow.x1; x < renderWindow.x2; x++) { OfxRGBAColourB *srcPix = pixelAddress(src, srcRect, x, y, srcRowBytes); if(srcPix) { dstPix->r = 255 - srcPix->r; dstPix->g = 255 - srcPix->g; dstPix->b = 255 - srcPix->b; dstPix->a = 255 - srcPix->a; } else { dstPix->r = 0; dstPix->g = 0; dstPix->b = 0; dstPix->a = 0; } dstPix++; } } // we are finished with the source images so release them gEffectSuite->clipReleaseImage(sourceImg); gEffectSuite->clipReleaseImage(outputImg); // if we aborted, then we have failed to produce and image, so say so if(gEffectSuite->abort(instance)) return kOfxStatFailed; // otherwise all was well return kOfxStatOK; }
The render action is where a plug-in turns its input images into output images. The first thing to note is that the effect is no longer a descriptor by an. For our simple example, this makes not much difference, however if the plug-in is maintaining private data, it is very important.
The first thing the render function does is to extract two properties from the
inArgs
property set. These are the time to render at, and the window to render over. Note that the render window is a 4 dimensional integer property.
Next the function fetches the output clip. A clip is a handle that is accessible by name and represents a sequences of images. The returned handle is valid for the lifetime of the instance, and so does not need releasing or deleting. Next an image is extracted from the clip. An image, unlike a clip, does need to be released when the plug-in is done with it.
Images are encapsulated as a property set, and so the ordinary property mechanism is used to fetch out information from the image. In this case three properties are needed, the
rowBytes
, or the number of bytes in a scan line (as there may be padding at the end), the image bounds, being the region where there is image data and a pointer to the image data. The source image is fetched in a similar way as the destination image.
Next the
void *
data pointers are cast to
OfxRGBAColourB
pointers, and we start iterating over the render window filling in output pixels. The render window must always be equal to or less than the bounds on the destination image. The inline function
pixelAddress
shows how pixel arithmetic is performed. Note the
abort
function from the image effect suite being called at every scan line to see if the rendering should be halted.
Finally, once we are done, the images are released and we return.