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 rayz_API_docs/source/nodes

  2. Compile the plugin for your platform:
    % make -f Makefile.irix Simple.so (for IRIX)
    % make -f Makefile.linux Simple.so (for 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/rayz1.0
    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.

    (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:

/////////////////////////////////////////
// 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_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;
    
    // get the input
    myInputs = cpiGetInputOp( 0, myTime, quality, scaleX, scaleY );

    
    // get the user parameter value
    cpiGetFloat ( &bright, "bright", myTime );

    // create metadata and copy value to it
    CPI_Metadata    nodeparms = cpiCreateMetadata();
    cpiSetMetaFloat32( nodeparms, "bright", bright );
    

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

    cpiDeleteMetadata( nodeparms );

    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
    NULL,                // NodeShutDown
    NULL,                // ParmChanged
    NULL,                // InputChanged
    NULL,                // GetRegion
    NULL,                // GetFullSize
    NULL,                // GetRange
    NULL,                // GetErrors
    SimpleExec,          // Node Execute
    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 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_Image    *result,
              CPI_Image      inputs[],
              CPI_Uint32     numInputs,
              CPI_Metadata   myParms )
{
    CPI_Float32    bright;

    
    // get the value from metadata
    cpiGetMetaFloat32( myParms, "bright", &bright );

    // 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 );
    

        
    return CPI_TRUE;
}

You can find this in the support/plugIns/src 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_Float32    myTime,
            CPI_Uint8      quality,
            CPI_Uint32     nodeOutput,
            CPI_Uint32     viewerOutput,
            CPI_Float32    scaleX,
            CPI_Float32    scaleY )

{
    CPI_Float32     dissolve;
    CPI_ImageOp     retval    = NULL;
    CPI_ImageOp     myInputs[2];
    
    myInputs[0] = cpiGetInputOp( 0, myTime, quality, scaleX, scaleY );
    myInputs[1] = cpiGetInputOp( 1, myTime, quality, scaleX, scaleY );

    cpiGetFloat ( &dissolve, "Dissolve", myTime );

    CPI_Metadata  nodeparms = cpiCreateMetadata();
       
    cpiSetMetaFloat32( nodeparms, "dissolve", dissolve );

    if ( myInputs[0] != NULL && myInputs[1] != NULL )
        retval = cpiAddImageOp( "twoinput", nodeparms, myInputs, 2 ); 
    else
        retval = false;

    cpiDeleteMetadata( nodeparms );

    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
    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
    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_Image    *result,
              CPI_Image      inputs[],
              CPI_Uint32     numInputs,
              CPI_Metadata   myParms )
{
    CPI_Float32    dissolve;

    cpiGetMetaFloat32( myParms, "dissolve", &dissolve );

    if ( result->myContext.bitsPerPel == 8 )
        simple( inputs, result, dissolve, (CPI_Uint8)255 );
    else if ( result->myContext.bitsPerPel == 16 )
        simple( inputs, result, dissolve, (CPI_Uint16)65535 );
    else
        simple( inputs, result, dissolve, (CPI_Float32)1.0F );
        
    return CPI_TRUE;
}


/////////////////////////////////////
// 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
    },
    SimpleProcess,           // image execution process
    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