The image operation is built around a structure that must be present. Here is an example from the CircleRamp node. This node is shipped with RAYZ as a plugin, not as a native node:
static RPI_ImageOpInfo opinfo = { { // this structure is required "circleramp", // command name "CircleRamp Image Operation", // not used "Silicon Grail", // author "1.0", // not used "", // not used "", // not used }, CircleRampCreateOPInstance, // create image op data CircleRampDestroyOPInstance, // destroy image op data CircleRampProcess, // image process (required) NULL, // input region CircleRampOutputRegion, // output region CircleRampControlProcessing, // control processing NULL, // setup processing NULL, // cleanup processing 0, // number of inputs required CPI_TRUE, // can this multiproc? CPI_FALSE, // handle disjoint regions? };This structure is defined in CPI/CPI_ImageOpProvider.h
Let's look at each of the fields in this structure:
Command Name -
This is the name of the image operation. This string is used to refer
to this image op, for example when unregistering it, or when creating
private data for it.
Author -
Any text string
Version -
This is not currently used by RAYZ, but is available for the programmer
Create Image Op - This is called (if it exists) when a new copy of the image operation is created. Typically, this is the place to allocate the variables that will be used to hold parameters coming in from the user, or to create a lookup table that the plugin will fill with values.
Create Destroy Op - This is called (if it exists) when an image operation is deleted. Typically this is the place to return any memory allocated in the create image op routine.
Image Process - (required) This is the name of the subroutine that is called when a CircleRamp is to be created. The declaration for this routine is always
CPI_Bool ProcessName( CPI_PrivateData handle, CPI_Image *result, CPI_Image inputs[], CPI_Uint32 numInputs )In most cases, this routine first unpacks the relevant parameters from the private data, and then performs its operation on the inputs, putting the result into (of course) *result.
Input region - (optional)
This routine describes the input needed to
complete the image operation.
If this function is not provided then the input region will be
assumed to be the same as the output region.
Output region - (optional)
This routine describes the region which will be the
result of this image operation. If the output size (or bit depth) is
not the same as the input, you should provide this function.
If this function is not provided, then the output region is
assumed to be the same as the input region.
Control processing - (optional) This routine is called if the 'disjoint regions' flag is set to CPI_TRUE. In that case, the control_processing() routine is passed the full sizes of all inputs, and it is the programmer's responsiblity to handle them correctly. See Regions.C as an example, as well as the section on region handling.
Setup processing - (optional)This can be used in conjunction with control_processing() above.
Cleanup processing - (optional)This would be used in conjunction with control_processing() above.
Number of inputs - This is the minimum number of inputs which are valid for this operation. RAYZ performs a quick sanity check with this number before proceeding.
Multi-processes - This is a boolean value which is CPI_TRUE if the plugin can multiprocess. In general, you can set this to CPI_TRUE if the calculations for any given pixel do not depend on the results of any other pixels - that is, if each output pixel can be calculated in parallel with every other.
However, thread safety is a complex issue, which is outside the scope of this documentation.
Disjoint regions - This is a boolean value which is CPI_TRUE if the plugin can handle disjoint regions. That means that the plugin can cope with images which are not the same size, or which are offset from each other. If this is CPI_FALSE, then only the intersection, or common, regions are passed to the Process function. See Regions.C as an example, and also the section on region handling.
static RPI_NodeInfo ninfo = { { "CircleRamp", // node name "Circleramp", // menu name "Silicon Grail", // author "v0.1" // version "", // help URL "" // icon name }, CircleRampInit, // initialize node (required) CircleRampShutDown, // shut down node NULL, // create instance NULL, // destroy instance CircleRampParmChanged, // parameter changed NULL, // input added NULL, // input changed NULL, // input removed CircleRampGetRegion, // return result region CircleRampGetFullSize, // return full size NULL, // return frame range NULL, // return errors CircleRampExec, // node execute (required) NULL, // input label NULL, // output label NULL, // viewer label NULL, // viewer outputs 0, // min inputs 0, // max inputs 1, // min outputs 1, // max outputs CPI_FALSE // can add more inputs };This structure is defined in /include/CPI/CPI_NodeProvider.h Any fields which are optional may be given a simple NULL value; otherwise, they are the names of routines that the programmer provides. Let's look at each of the fields in this structure:
Node Name - A text string which labels the operation. This is the name that will be registered/unregisterd with the Exec (see later). This is also the name of the nodes that will be created on the Worksheet, eg node_name1, node_name2, etc
This is also the string which
would be used in scripting operations involving this function. When
a .rayz file is saved, this is the string which defines the operation
there. In the given example, the plugin would appear in the .rayz
file (or be accessed by the script writer) as
given_name = circleramp( ) { }
Menu Name - This is the name which the user will see on the RAYZ menu. For file formats, this is the string which will appear in the popup menu on the Image In/Out nodes.
Initialize - Called to initialize the node. Typically, this means setting up the node parameter UI. This is called only once, when RAYZ starts up.
ShutDown - (optional) Called to exit the node. This is called only once, when RAYZ shuts down.
Create Instance - (optional) Called when a node of this type is created. Any other node initialization you might need to do would go here. For example, you might use this to open a file and read in a table of values, so that the Image Op doesn't have to open and read files later.
Destroy Instance - (optional) Called when this node is deleted. If you've allocated any memory for the node, this would be the place to free it.
Parameter Changed - (optional) Called with the name of the parameter whose value has just changed. This is used to make adjustments based on the user changing a parameter; for example, here is where you would enable/disable parameters based on menu choices.
Input Added - (optional) Called with the number of the input (counting from 0) which was added. Useful for handling nodes where the number of inputs is not fixed (such as MultiComp).
Input Changed - (optional) Called with the number of the input (counting from 0) which has changed (ie, been replaced or deleted). If the plugin depends on info from the input(s), this is where you get that info.
Input Removed - (optional) Called with the number of the input (counting from 0) which was removed. Useful for handling nodes where the number of inputs is not fixed (such as MultiComp).
GetRegion - (optional) Describes the size and bit depth of the result image from this node operation. This is mostly used for UI purposes, to report image information to the status bar.
GetFullSize - (optional) Describes the full size of the result image. If this function is not provided, the output is assumed to be the same size as the input. This is mostly used for UI purposes, to report image information to the status bar.
GetRange - (optional) Delivers the start and end frame numbers of the output of this node.
GetErrors - (optional) This is called by RAYZ to enable you to check for user errors. This allows the programmer to trap and handle errors before RAYZ begins to process the image operation.
NodeExecute - (required) This does the actual work. This is called with the private data pointer, the input image(s) and a pointer to the output image.
InputLabel - (optional) Returns the label of the given input number.
OutputLabel - (optional) Returns the label of the given output number.
ViewerLabel - (optional)
A node can have more than one output, and each output can have more
than one viewer. (They are called viewers because you can't pass them
on for further processing, you can only view them).
For example, take the Ultimatte GK node. This has one output,
(that is, one output connector),
but that output has three view choices: the input image, the output image
and the filter area.
This routine returns the label for the given viewer output. Note that
you cannot rename the two default viewer outputs (see below), only the
extra ones which you
have added. You set this value with the ViewerOutputs function below.
See also Chapter 4 - Multiple Inputs and Outputs.
ViewerOutputs - (optional)
This returns the number of viewers available for each output of
the node. Most nodes only have a single output, but that output can have
many viewers (for example, the Ultimatte GK node). By default, there
are always 2 - the input and the output of the node. When you add more,
you add to these two.
See also Chapter 4 - Multiple Inputs and Outputs.
Growable flag - If this is set to CPI_TRUE, the node will grow to allow more inputs to be added. An example of this in RAYZ is the MultiComp node.
Init Called when RAYZ is started (so once per node type) CreateInstance Allocate a copy of the node and allocate any resources Callbacks: Supplied by programmer to plugin ParamChanged When a parameter to the node changes value InputChanged When an input image to the node changes Information: Supplied by programmer to RAYZ GetRegion GetFullSize GetRange GetErrors Exec When an image or computation is requested by user DestroyInstance Delete the node and any resources it allocated ShutDown Called when the user quits RAYZ For Image Ops, or script commands, a similar but much shorter list is used: CreateInstance Create the image op InputRegion Tell RAYZ how big the input region should be OutputRegion Tell RAYZ how big the result will be ControlProcessing Oversee the image processing, if required Setup Available entry point for preprocessing activities Process Do the actual operation The minimum required is: Node Init Exec Shutdown Image Op Process
typedef struct { CPI_Int32 sizeX; CPI_Int32 sizeY; CPI_Int32 offsetX; CPI_Int32 offsetY; CPI_Int32 channels; CPI_Int32 skipPixels; // see note below CPI_Int32 bitsPerPel; // passed in as 8, 16, or 32 CPI_Float32 pixelRatio; CPI_Int32 dataType: // 0 = linear, 1 = log, 2 = video CPI_Float32 referenceBlack; CPI_Float32 referenceWhite; CPI_Float32 linearWhite; CPI_Float32 displayGamma; CPI_Float32 filmGamma; CPI_Float32 videoGamma; CPI_Bool isPremultiplied; CPI_Float32 backingRed; // backing color CPI_Float32 backingGreen; CPI_Float32 backingBlue; CPI_Float32 backingAlpha; CPI_Float32 backingOther; } CPI_ImageContext; typedef struct { CPI_ImageContext myContext; void *data; } CPI_Image;
The concept of metadata, as a method for passing parameters, has gone away. Instead, the plugin writer now defines a private data structure; this structure is then allocated and filled with values. A pointer to that block of values is then passed around to each function which might require it.
Typically, this looks like:
// define the parameter block to be passed typedef struct _circleState { CPI_Int32 center[2]; CPI_Float32 radius; CPI_Bool oval; } circleState; // in node exec, allocate a block and fill it Exec () { // get input(s) and parameter values from GUI ... // declare the variables, and allocate the parameter block CPI_PrivateData opData; circleState *circleData = NULL; opData = cpiCreatePrivateData( "cropcircle" ); circleData = (circleState *)opData; // get params from GUI and pass to param block circleData->center[0] = // some calculation... circleData->center[1] = // some calculation... cpiGetFloat( &circleData->radius, "radius", myTime ) cpiGetBool( &circleData->oval, "oval", myTime ) // add image operation to list // If a node takes no parameters, the data argument should be NULL if ( myInputs != NULL ) retval = cpiAddImageOp( "cropcircle", circleData, &myInputs, 1 ); else retval = NULL; return retval; }In the Exec function of the node, the input image(s) and parameter values are obtained. Then a parameter structure is created, and filled with whatever information needs to be passed to the image operation. The parameters are passed via the cpiAddImageOp() function call.
Note: If the image operation takes no parameters, you should pass NULL as the parameter argument.
To retrieve the values in the Image Operation Process function, a pointer to the values will be passed in as the first argument. Typically, you would retrieve the values like this:
#define MAX8 (CPI_Uint8)255 #define MAX16 (CPI_Uint16)65535 #define MAXF (CPI_Float32)1.0F Process ( CPI_PrivateData handle, CPI_Image *result, CPI_Image inputs[], CPI_Uint32 numInputs ) { // unload parameter values centerx = handle->center[0]; centery = handle->center[1]; radius = handle->radius; oval = handle->oval; ... // perform the operation. Often this is done via a templated function // which is called like this if ( result->myContext.bitsPerPel == 8 ) doThing( inputs, result, center, radius, oval, MAX8 ); else if ( result->myContext.bitsPerPel == 16 ) doThing( inputs, result, center, radius, oval, MAX16 ); else doThing( inputs, result, center, radius, oval, MAXF ); }The mechanism for allocating the parameter block is the pair of functions called when the Image Op is created and destroyed. This is done at the Image Op level because Image Ops can exist without nodes, for example in a scripted version of RAYZ.
These functions frequently look as follows:
// allocate a parameter structure. Note the use of the RAYZ routine // cpiAlloc(). This ensures compatibility across operating systems. static CPI_PrivateData CropCircleCreateOpInstance( void ) { circleState *retval = (circleState *)cpiAlloc( sizeof( circleState ) ); return (CPI_PrivateData)retval; } // return allocated memory. Note the use of the RAYZ routine cpiFree(). // This ensures compatibility across operating systems. static CPI_Bool CropCircleDestroyOpInstance( CPI_PrivateData handle ) { CPI_Bool retval = CPI_FALSE; circleState *state = (circleState *)handle; if ( NULL != state ) { cpiFree( state ); retval = CPI_TRUE; } return retval; }