General Node Description

The Structure of a Node Plugin

In RAYZ, nodes are made of up to three parts:
  1. The image operation - defines the actual work. This is optional, as RAYZ internal IMs can be used instead or as well.
  2. The node interface - this defines the parameters to the user.
  3. The overlay - this is graphics, which may be interactive, drawn on the image itself. Typically these are used to provide visual parameter settings. The Crop node is an example of this. Overlays are optional.
The distinction between these is that the image operation (IM) is the actual arithmetic that creates or manipulates images, while the node is the interface presented to the user. These are separate because some aspects, such as scripting, do not require the node - they just use the operation. This means that when you build a RAYZ plugin, you are also creating a script command that can be used outside of RAYZ.

The Image Operation Structure

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.

The Node Info Structure

The node is built around a structure that must be present. Here is an example from the CircleRamp node:
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.


How a Plugin Executes

User-supplied routines are called in the following order, or in response to the given events:
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
    

How an Image is Defined

In RAYZ, an image is defined as a context and some data. The format for each of these is given in CPI_Types.h:
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;

Passing Parameters with Private Data

Because in RAYZ the Node and the Image Operation are separate things, there needs to be a mechanism to get parameters from the one to the other. That mechanism has changed completely from RAYZ 2.0 to RAYZ 2.2.

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;
}

[Previous Page] [Next Page]
[Table of Contents] [Index]

Copyright © 2002 Silicon Grail Inc.
736 Seward Street, Hollywood, CA 90038