Node Quickstart

Let's get some instant gratification, and pile right in and make a plugin node. In this section we do three things:

Part 1 - A Trivial Node

In this first step, the plugin code exists already, so the steps are very simple:

  1. Go to the location where you unpacked the SDK (Software Development Kit). Let's assume it's /usr/home/RAYZ_SDK. Then move into example/Nodes.
    % cd /usr/home/RAYS_SDK/example/Nodes

  2. Compile the plugin for your platform. For IRIX and linux, the appropriate makefile is already in the directory.
    % make Simple.so (for IRIX/linux)

  3. Point RAYZ at Simple.so. To do this, you need to define the environment variable RAYZ_PLUGIN_PATH. You will want to do something like this
    setenv RAYZ_PLUGIN_PATH "RAYZ_INSTALL/plugIns:/your/plugin/dir"
    where
    RAYZ_INSTALL is the location of RAYZ, usually /usr/grail/rayz2.2
    and
    /your/plugin/dir is the location where you are compiling your plugins. RAYZ will search directories recursively, looking for plugins to load at startup.

    Alternatively, you can launch RAYZ, and add your plugin path via the Preferences dialog. Go to Edit->Preferences->File Paths->Plugins, and add the string
    |/your/plugin/dir
    after the existing string. Note the vertical bar (|) character which separates paths.

    Then exit RAYZ.

    (note: this is for IRIX/linux. For Windows NT, you will have to modify the above to your own compiling setup. Environment variables are also set up differently on NT. More detail is given in Compiling and Installing Plugins

  4. run RAYZ
When you hit the spacebar (or look in the Plugins menu) you should see a node called 'menuname'. Selecting this node will create a node called 'nodename' and place it on the worksheet. In the Node Panel, you will see the parameters for this node, which in this case consists entirely of two lines of controls. Since our node is so simple, it has no parameters (Figure 1).


Figure 1: A node with no parameters

Things to note:
There is a distinction between the 'node name', which is sometimes called the internal name, and the 'menu name', which is the string used in the node menu. Notice how, in the Node Panel, the node is shown as being of type 'menuname', and of default name 'nodename1'. You will also see in the sample code that there is a third string, 'opname', which is the name of the image operation that the node performs.

This is a key distinction in RAYZ - a NODE is a graphical thing you move around on the worksheet, and which you can change the parameters for interactively. An IMAGE OPERATION (often called an IM) is the actual code that does something to the image. These are separate things. This is because RAYZ is scriptable - the image op is basically the scripting command version of the node.

By convention in RAYZ, the menu name is capitalized, and the node name is the same name, in lower case. Sometimes, the menu name is not the same as the node name, especially for File Formats - we will see that later.


Part 2 - Adding A Parameter

So far, our node doesn't do much - it just copies its input to its output. Let's add a parameter to the node, in this case a single value between 0 and 1 to multiply the incoming image by.

Here is the plugin code, with new lines highlighted:


////////////////////////////////////////
// In order to pass parameters between the COP level and
// the IM level, we define a structure of values. An
// instance of this structure will be passed down to the
// IM (IMage operation)

typedef struct _simpleState
{
    CPI_Float32  brightness;
} simpleState;


/////////////////////////////////////////
// The init routine is called once, and typically is used to
// define the user input controls


static CPI_Bool
SimpleInit( void )
{
    cpiAddFloat( "bright", "Brightness", 1.0, 0.0, 1.0,
                  CPI_PARM_ANIMATABLE | CPI_PARM_CLAMPMIN,
                  "This controls the brightness",
                  NULL );
    
    return CPI_TRUE;
}



//////////////////////////////////////////
// This is called when an image is required
// The basic structure of these routines is:
// 1. get the inputs
// 2. get the parameters from the user interface
// 3. allocate metadata and copy the parameters to it
// 4. add the image op(s) to the list of operations

CPI_ImageOp
SimpleExec( CPI_PrivateData  handle,
            CPI_Float32      myTime,
            CPI_Uint8        quality,
            CPI_Uint32       nodeOutput,
            CPI_Uint32       viewerOutput,
            CPI_Float32      scaleX,
            CPI_Float32      scaleY )

{
    CPI_Float32     bright;
    CPI_ImageOp     retval    = NULL;
    CPI_ImageOp     myInputs  = NULL;
	
	CPI_PrivateData  opData     = NULL;
	simpleState     *simpleData = NULL;
	
    
	// Note: error checking omitted for clarity
    // get the input
    myInputs = cpiGetInputOp( 0, myTime, quality, scaleX, scaleY );

    
	// define and allocate a parameter structure
	opData      = cpiCreatePrivateData( "oneparam" );
	simpleData  = (simpleState *)opData;

    // get the user parameter value and put it into the parm structure
    cpiGetFloat ( &simpleData->brightness, "bright", myTime );
    

    // if there was input, add this image operation to the list
    if ( myInputs != NULL )
           retval = cpiAddImageOp( "oneparam", simpleData, &myInputs, 1 );

    return retval;
}


////////////////////////////////////////////
// This structure, which is required, defines to RAYZ
// the various routines which are part of this plugin.
// Routines which are not required are set to NULL

static RPI_NodeInfo ninfo = {
    {
        "oneparam",         // node name
        "Oneparam",         // menu name
        "Joe Programmer",   // author
        "v1.0",             // version
        "",                 // help URL
        ""                  // icon name
    },
    SimpleInit,         // NodeInit     (required)
    NULL,               // NodeShutDown
    NULL,               // NodeCreateInstance
    NULL,               // NodeDestroyInstance
    NULL,               // ParmChanged
    NULL,               // InputAdded
    NULL,               // InputChanged
    NULL,               // InputRemoved
    NULL,               // GetRegion
    NULL,               // GetFullSize
    NULL,               // GetRange
    NULL,               // GetErrors
    SimpleExec,         // Node Execute (required)
    NULL,               // InputLabel
    NULL,               // OutputLabel
    NULL,               // ViewerLabel
    NULL,               // ViewerOutputs
    1,                  // min inputs
    1,                  // max inputs
    1,                  // min outputs
    1,                  // max outputs
    CPI_FALSE           // is growable (eg Mcomp)
};

//////////////////////////////////////////////////////////////////////
// Image Op Functions ////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

// This is the actual image manipulation routine. It is a templated
// function so that we can write it once, and let the compiler build
// a version for each of 8, 16 and float pixel types.

template 
void simple( CPI_Image     &input,
             CPI_Image     *result, 
             CPI_Float32    bright,
             T              max)
{
    CPI_Uint32    i, x, y, endX, endY;
    CPI_Uint32    chans = result->myContext.channels;
    T            *pin, *pout;

    pin     = (T *)input.data;
    pout    = (T *)result->data;
    endX    = result->myContext.offsetX + result->myContext.sizeX;
    endY    = result->myContext.offsetY + result->myContext.sizeY;
    
    for ( y = result->myContext.offsetY; y < endY; y++ )
    {
        for ( x = result->myContext.offsetX; x < endX; x++ )
        {
            // multiply each channel by the passed value
            for ( i = 0; i < chans; i++ )
            {
                *pout = (T) (*pin * bright);
                pin++;
                pout++;
            }
        }
    }
}



////////////////////////////////////////
// This is called when a new node of this type is created.
// Typically, this is the place that allocates a particular
// instance of the parameter-holding structure defined earlier.

static CPI_PrivateData
SimpleCreateOpInstance( void )
{
    simpleState *retval = (simpleState *)cpiAlloc( sizeof( simpleState ) );

    return (CPI_PrivateData)retval;
}


////////////////////////////////////////
// This is called when the node is deleted. Typically, this is where you
// return the memory allocated in the CreateOp routine defined above.

static CPI_Bool
SimpleDestroyOpInstance( CPI_PrivateData     handle )
{
    CPI_Bool     retval = CPI_FALSE;
    simpleState *state  = (simpleState *)handle;

    if ( NULL != state )
    {
        cpiFree( state );
        retval = CPI_TRUE;
    }

    return retval;
}



/////////////////////////////////////////
// This is called to execute the image op
// The basic structure of these routines is
// 1. unload parameter values from metadata
// 2. call the appropriate routine depending on bit depth

CPI_Bool
SimpleProcess( CPI_PrivateData  handle,
               CPI_Image       *result,
               CPI_Image        inputs[],
               CPI_Uint32       numInputs,
               CPI_Metadata     myParms )
{

    CPI_Float32    bright;
    simpleState   *state  = (simpleState *)handle;
    CPI_Bool       retval = CPI_FALSE;

    if ( state != NULL )
    {
        // get the value from the private data
        bright = state->brightness;

        // pass it to the appropriate routine
        if ( result->myContext.bitsPerPel == 8 )
            simple( inputs[0], result, bright, (CPI_Uint8)255 );
        else if ( result->myContext.bitsPerPel == 16 )
            simple( inputs[0], result, bright, (CPI_Uint16)65535 );
        else
            simple( inputs[0], result, bright, (CPI_Float32)1.0F );
    }
    else
        cpiError( "Couldn't find SimpleParam private data" );
        
    return retval;
    
}

/////////////////////////////////////
// This structure defines the image op (IM) that you
// make. It is required if you are creating your own IM.
// Required entries are marked, the rest are optional. Fill in
// NULL if there is no entry for an optional function.

static RPI_ImageOpInfo opinfo =
{
    {                       // this structure is required
        "oneparam",         // command name
        "",                 // not used
        "Joe Programmer",   // author
        "0.1"               // version
    },

    SimpleCreateOpInstance, // create op
    SimpleDestroyOpInstance,// destroy op

    SimpleProcess,          // image execution process (required)
    NULL,                   // InputRegion
    NULL,                   // OutputRegion,
    NULL,                   // ControlProcessing
    NULL,                   // SetupProcessing
    NULL,                   // CleanupProcessing
    1,                      // min number of inputs required
    CPI_TRUE,               // Can this plugin multiproc?
    CPI_FALSE               // Handle disjoint regions?
};

You can find this in the example/Nodes directory if you don't want to type it yourself - it's called SimpleParam.C. Compile it by typing
% make SimpleParam.so
Again, drop it into your plugin directory. Now when you run RAYZ, and add one of these new nodes (which are called 'oneparam' in the menu), you should see a Node Panel that looks like this:


Figure 2: A node with a single parameter


Part 3 - Adding Multiple Inputs

Let's modify this node to take a second input. Here is the code that you need to add or modify:



//////////////////////////////////////////
// label the node inputs - this is optional
// Input is the number of the input you want to provide a
// label for, starting at 0 - this label shows up in the
// status line when the user mouses over the connector.
// The first letter of each string is used to label the input
// connector.

const char *
SimpleInputLabel( CPI_Uint32    input)
{
    static const char   input1[] = {"foreground image"};
    static const char   input2[] = {"background image"};

    if ( input == 0 )
        return input1;
    else
        return input2;
}


//////////////////////////////////////////
// This is called when the node is actually executed

CPI_ImageOp
SimpleExec( CPI_PrivateData handle,
            CPI_Float32     myTime,
            CPI_Uint8       quality,
            CPI_Uint32      nodeOutput,
            CPI_Uint32      viewerOutput,
            CPI_Float32     scaleX,
            CPI_Float32     scaleY )

{
    CPI_ImageOp     retval    = NULL;
    CPI_ImageOp     myInputs[2];
    
    // get the two input images
    myInputs[0] = cpiGetInputOp( 0, myTime, quality, scaleX, scaleY );
    myInputs[1] = cpiGetInputOp( 1, myTime, quality, scaleX, scaleY );

    if ( myInputs[0] != NULL && myInputs[1] != NULL )
    {
        // These 4 lines define and allocate a block of data as
        // defined in the earlier typedef
        CPI_PrivateData  opData     = NULL;
        simpleState     *simpleData = NULL;

        opData      = cpiCreatePrivateData( "twoinput" );
        simpleData  = (simpleState *)opData;

        // error checking omitted for clarity
        cpiGetFloat ( &simpleData->dissolve, "dissolve", myTime );
        retval = cpiAddImageOp( "twoinput", nodeparms, myInputs, 2 ); 
    }
    else
        cpiError( "Missing required input(s)" );

    return retval;
}


////////////////////////////////////////////
// This structure defines aspects of the node - user subroutines,
// number of inputs, etc

static RPI_NodeInfo ninfo = {
    {
        "twoinput",              // node name
        "Twoinput",              // menu name
        "Joe Programmer",        // author
        "v1.0",                  // version
        "",                      // help URL
        ""                       // icon name
    },
    SimpleInit,         // NodeInit         (required)
    NULL,               // NodeShutDown
    NULL,               // NodeCreateInstance
    NULL,               // NodeDestroy
    NULL,               // ParmChanged
    NULL,               // InputAdded
    NULL,               // InputChanged
    NULL,               // InputRemoved
    NULL,               // GetRegion
    NULL,               // GetFullSize
    NULL,               // GetRange
    NULL,               // GetErrors
    SimpleExec,         // Node Execute     (required)
    SimpleInputLabel,   // InputLabel
    NULL,               // OutputLabel
    NULL,               // ViewerLabel
    NULL,               // ViewerOutputs
    2,                   // min inputs
    2,                  // max inputs
    1,                  // min outputs
    1,                  // max outputs
    CPI_FALSE           // is growable (eg Mcomp)
};



//////////////////////////////////////////////////////////////////////
// Image Op Functions ////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

// This is the actual image manipulation routine. It is a templated
// function so that we can write it once, and let the compiler build
// a version for each of 8, 16 and float pixel types.

template 
void simple( CPI_Image      inputs[],
             CPI_Image     *result, 
             CPI_Float32    dissolve,
             T              max)
{
    CPI_Uint32     i, x, y, endX, endY;
    CPI_Uint32     chans = result->myContext.channels;
    T             *pinA, *pinB, *pout;
    CPI_Float32    oneminus;

    pinA    = (T *)inputs[0].data;
    pinB    = (T *)inputs[1].data;
    pout    = (T *)result->data;
    endX    = result->myContext.offsetX + result->myContext.sizeX;
    endY    = result->myContext.offsetY + result->myContext.sizeY;
    oneminus =  1.0 - dissolve;
    
    // this assumes that the two images are the same size, which is a
    // bad assumption
    for ( y = result->myContext.offsetY; y < endY; y++ )
    {
        for ( x = result->myContext.offsetX; x < endX; x++ )
        {
            // do the math
            for ( i = 0; i < chans; i++ )
            {
                *pout = (T) ((*pinA * dissolve) + (*pinB * oneminus));
                 pinA++;
                 pinB++; 
                 pout++;
            }
        }
    }
}


/////////////////////////////////////////
// This is the image operation. 

CPI_Bool
SimpleProcess( CPI_PrivateData   handle,
               CPI_Image        *result,
               CPI_Image         inputs[],
               CPI_Uint32        numInputs,
               CPI_Metadata      myParms )
{
    CPI_Float32    bright;
    simpleState   *state  = (simpleState *)handle;
    CPI_Bool       retval = CPI_FALSE;

    if ( state != NULL )
    {
        // get the value from the private data
        bright = state->brightness;


        // pass it to the appropriate routine
        if ( result->myContext.bitsPerPel == 8 )
            simple( inputs, result, bright, (CPI_Uint8)255 );
        else if ( result->myContext.bitsPerPel == 16 )
            simple( inputs, result, bright, (CPI_Uint16)65535 );
        else
            simple( inputs, result, bright, (CPI_Float32)1.0F );
    
        retval = CPI_TRUE;
    }
    else
        cpiError( "Couldn't find Simple private data" );
        
    return retval;
}


/////////////////////////////////////
// Structure which defines the image op 

static RPI_ImageOpInfo opinfo = 
{
    {
        "twoinput",          // command name
        "",                  // not used
        "Joe Programmer",    // author
        "0.1",               // version
        "",                  // help URL
        ""                   // icon name
    },
    SimpleCreateOpInstance,  // CreateOpInstance
    SimpleDestroyOpInstance, // DestroyOpInstance
    SimpleProcess,           // image execution process   (required)
    NULL,                    // InputRegion
    NULL,                    // OutputRegion,
    NULL,                    // ControlProcessing
    NULL,                    // SetupProcessing
    NULL,                    // CleanupProcessing
    2,                    // min number of inputs required
    CPI_TRUE,                // Can this plugin multiproc?
    CPI_FALSE                // Disjoint regions?
};

Once again, this has been done for you and placed in SimpleTwoInput.C. Compile it by typing
% make SimpleTwoInput
Run RAYZ again, and notice that the node now looks like this:

In this example, we have required this node to have two inputs (since they are not connected, the node is marked with red, to indicate an error). Notice that the inputs are labeled 'f' and 'b' - this is under your control, as you can see in the complete source listing. The routine called SimpleInputLabel shows how this is done.

It is also possible to specify one (or more) optional inputs. This is done by setting the appropriate values in the NodeInfo structure. For example, this structure defines a node which takes 3 inputs, only 1 of which is required. Relevant lines are shown in bold:

static RPI_NodeInfo ninfo = {
    {
        "twoinput",          // node name
        "Twoinput",          // menu name
        "Joe Programmer",    // author
        "v1.0",              // version
        "",                  // help URL
        ""                   // icon name
    },
    SimpleInit,         // NodeInit
    NULL,               // NodeShutDown
    NULL,               // ParmChanged
    NULL,               // InputChanged
    NULL,               // GetRegion
    NULL,               // GetFullSize
    NULL,               // GetRange
    NULL,               // GetErrors
    SimpleExec,         // Node Execute
    SimpleInputLabel,   // InputLabel
    NULL,               // OutputLabel
    NULL,               // ViewerLabel
    NULL,               // ViewerOutputs
    1,                  // min inputs
    3,                  // max inputs
    1,                  // min outputs
    1,                  // max outputs
    CPI_FALSE           // is growable (eg Mcomp)
};

By definition, required inputs come are drawn first (topmost), followed by the optional ones.

This quick overview should give you a feeling for how to write and compile node plugins in RAYZ. Of course, you can do a lot more than this - you can define overlays (graphic controls) on the image, you can define new image file formats, and you can define new image display controls (including LUTS) for the image viewer.

All of these are explained in the rest of this documentation.


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

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