typedef struct { unsigned int sizeX; unsigned int sizeY; unsigned int fullX; unsigned int fullY; unsigned int offsetX; unsigned int offsetY; unsigned int channels; float time; CPI_PelType pelType; unsigned int input; } CPI_ImageContext; typedef struct { CPI_ImageContext info; void *data; } CPI_Image; typedef enum { P_INT8, P_INT16, P_FLOAT32, P_UNKNOWN } CPI_PelType;The image data is stored in scan-line order, starting from the upper left corner of the image. The format of each scanline is
The size of each pixel is dependent on the format of the pixels, and is
given by this table
pelType | bytes |
---|---|
P_INT8 | 1 |
P_INT16 | 2 |
P_FLOAT32 | 4 |
This information is also accessible via the call cpiGetByteSize() (see Chapter 6, Section 3 )
So to access an individual pixel whose address is (x, y), the offset into the *data array would be y*sizeX*pelsize*channels + x*pelsize*channels For example, this fragment sets the point (xpos, ypos) to [0, 0, 0, 0].
template <class T> void clearPixel(T *image, unsigned int offset, unsigned int channels) { int i; image += offset; for (i = 0; i < channels; i++) { *image = (T)0; image++; } } CPIDSOEXPORT int upiProcessImage( CPI_Image *result ) { int xpos, ypos, pelsize, channels, offset; ... assume that coming in to here we have the x and y position of the pixel in question in (xpos, ypos) channels = result->info.channels; switch( result->info.pelType) { case P_INT8: pelsize = channels * 1; offset = ypos * result->info.sizeX * pelsize + xpos * pelsize; clearPixel((unsigned char *)result->data, offset, channels); break; case P_INT16: pelsize = channels * 2; offset = ypos * result->info.sizeX * pelsize + xpos * pelsize; clearPixel((unsigned short *)result->data, offset, channels); break; case P_FLOAT32: pelsize = channels * 4; offset = ypos * result->info.sizeX * pelsize + xpos * pelsize; clearPixel((float *)result->data, offset, channels); break; default: cpiError("Unknown pixel type"); return 1; } return 0; }The complete version of this plugin is in example file SetPixel.C
int upiResultInput( CPI_ImageContext *result )can be used. The input argument can be used to know the state of the current image (sizeX, sizeY, etc). Then the routine returns one of the following:
INPUT_A (default) INPUT_B INPUT_C INPUT_D RESULT_SEPARATEThe first 4 refer to the first 4 inputs of the node; they tell Chalice that the results will be stored in that input. RESULT_SEPARATE tells Chalice that a new output will have to be allocated. For example:
CPIDSOEXPORT int upiResultInput( CPI_ImageContext *result ) { return RESULT_SEPARATE; }This will be the case when the result cannot be returned in the same memory space as was passed in; in particular, nodes which change the size, type and/or number of channels in an image, must allocate and return new memory as the output.
/* * Calculate our output size from the input size * and parameters. */ CPIDSOEXPORT int upiOutputContext( CPI_ImageContext *info, float time ) { /* Get the input information */ if( cpiInputContext( INPUT_A, info, time ) == 0 ) { /* Calculate our output size */ float t; cpiGetFloat( "Tile X", time, &t ); info->fullX *= t; cpiGetFloat( "Tile Y", time, &t ); info->fullY *= t; /* Make sure our image is at least one pixel */ if( info->fullX <= 0 ) info->fullX = 1; if( info->fullY <= 0 ) info->fullY = 1; return 0; } else { cpiError( "Couldn't get input size" ); return 1; } } /* * When our parameters (Tile X and Tile Y) change, we * need to tell chalice our output info has changed. */ CPIDSOEXPORT void upiParameterChanged( char *name ) { cpiInvalidateOutput(); } /* * We can't use the input as our result image */ CPIDSOEXPORT int upiResultInput( CPI_ImageContext *result ) { return RESULT_SEPARATE; }
Thus it is possible to specify multiple blocks from the same input
image as required regions. Each call to cpiNeedRegion() defines a region,
in order, which can be retrieved with cpiCookRegion(). Regions are numbered
from 0, so the first call to cpiNeedRegion() will define a region which
will be returned with cpiCookRegion(0, &input), the next will come from
cpiCookRegion(1, &otherinput), etc.
Here is a table which shows how regions map to node inputs:
Input | Result in | Which Cook |
---|---|---|
0 | *result | None - passed in |
1 | region 0 | first cook |
2 | region 1 | second cook |
N | region N-1 | Nth cook |
Usually, regions do conform to entire images, and cpiNeedRegion() is
used to define multiple inputs to a node. For example, here is a code
fragment from a node that takes 2 inputs; the first input, by default, is
copied into *result before it is passed to upiProcessImage(). The second
input comes from the call to cpiCookRegion(0, &input). Chalice knows what
to return from this call, because by default the next region to cook is
the second input to the node. Calls to cpiNeedRegion() redefine this
default mapping.
CPIDSOEXPORT void upiNumberOfInputs( int *min, int *max ) { *min = 2; *max = 2; } CPI_PluginType upiPluginType( void ) { return T_MATTE; } CPIDSOEXPORT int upiProcessImage( CPI_Image *result ) { CPI_Image input; int pixels; // result has a copy of input 0 (by default) // get the other image by cooking region 0 (not 1) // see documentation for why if( cpiCookRegion( 0, &input ) != 0 ) return -1; pixels = result->info.sizeX * result->info.sizeY * result->info.channels; switch( result->info.pelType ) { case P_INT8: dosomething((unsigned char *)result->data, (unsigned char *)input.data, pixels); break; case P_INT16: dosomething((unsigned short *)result->data, (unsigned short *)input.data, pixels); break; case P_FLOAT32: dosomething((float *)result->data, (float *)input.data, pixels); break; default: cpiError( "Unknown pixel type" ); return 1; } return 0; }There is also a cpi routine, cpiNumberOfInputs(), which returns to the user program the number of currently connected inputs. This information is potentially necessary in multi-input nodes, for example to decide whether an optional control image is connected.
The routines for doing this are upiCheckInputSize() and upiCheckInputDepth(). Each routine is called before the node begins to cook - if the routine exists and returns a non-zero value, an error is returned and cooking stops. If you detect an error, you should call cpiError() and return a non-zero value.
If you do not care if sizes are different, for example, you could simply write
int upiCheckInputSize( CPI_ImageContext *result ) { return 0; }
For example, in a 150 frame animation at 24 frames per second, if the
user requests a monitor from a node at frame 50, then the time of that
request would be 50 / 24 = 2.08333.
Time can be converted between frames and seconds with the following
utilities:
int cpiGetFrame( float time ) Returns the frame value of the given time (in seconds). float cpiGetTime( int frame ) Returns the time (in seconds) of the given frame.When cooking an image, Chalice assumes that the input frame the node needs is the one which occurs at the current time. But that does not need to be true. It is possible to request an input image from any time in the overall sequence. The routine upiRegionsNeeded() is used to tell Chalice which input is required for the node.
For example, say we want a node which randomizes its input frames, delivering as output any random frame from the sequence plugged into its input. Then we would do this:
CPIDSOEXPORT int upiResultInput( CPI_ImageContext *result ) { return RESULT_SEPARATE; } CPIDSOEXPORT void upiRegionsNeeded( CPI_ImageContext *result ) { int seed; if( cpiGetInteger( "Seed", result->time, &seed ) != 0 ) { cpiError( "Could not get seed" ); return; } srand( seed ); long start, end; if( cpiInputFrameRange( 0, &start, &end ) != 0 ) { cpiError( "Couldn't get input frame range" ); return; } long frame = rand() % (end-start) + start; result->input = 0; result->time = cpiGetTime( frame ); cpiNeedRegion( result ); } CPIDSOEXPORT int upiProcessImage( CPI_Image *result ) { CPI_Image input; if( cpiCookRegion( 0, &input ) != 0 ) return -1; int pelsize; if (result->info.pelType == P_INT8) pelsize = 1; else if (result->info.pelType == P_INT16) pelsize = 2; else if (result->info.pelType == P_FLOAT32) pelsize = 4; else cpiError( "Unknown pixel type" ); int memsize = result->info.sizeX * result->info.sizeY * result->info.channels * pelsize; // copy input into result memcpy( result->data, input.data, memsize ); return 0; }The three upi routines work together to accomplish this. First, upiResultInput() tells Chalice that a separate image will be passed to the output.
Note that *result always points to a place to put the result image. The difference between setting RESULT_SEPARATE and not is that, if it is set, the memory that *result points to will be separate from the memory pointed to by the input image. If it is not set, the memory location that contains the input image will be the same memory location pointed to by *result. Thus your node will be more memory efficient if you can avoid using RESULT_SEPARATE unless absolutely necessary.
These extensions are under development. If you have an immediate need for this capability, please contact Silicon Grail support.
int cpiNumProcessors( void )
which returns the number of available processors. The second is
void cpiMultiProcess(void (*func)(int, int, void *), void *arg)
which hands off to Chalice the name of a routine (func) to be called once per available processor. This function will get all of its arguments via the void *arg pointer which usuallly points to a structure containing data.
The function itself is called by Chalice once for each available processor; each call looks like
void func(int proc, int numProc, void *arg)
The first argument is the processor number that is being called - these start at 0 (zero). numProc is the total number of processors available, and *arg points to a block of data which contains any information that func() needs to complete its work.
One example of such data is pointers to the area of image memory that this particular instance of func() should work on. Chalice does not divide up images and hand each piece off to a separate call to func() - it is the programmer's job to do that.
For an example of this division, and calls to multiple versions of func(), see the source to MultiBright.C
However, it is possible to use the upi function upiMessageInfo() to pass information back to the user via the node's info button. When the info button is clicked by the user, whatever string returned by this function is added to the block of information shown to the user. For example:
char *upiMessageInfo(float time) { static char help[] = "This message can be used as a\n\ help message, which can run on multiple lines\n\ Is that ok?"; return(help); }Note that newline characters (\n) can be used, and that the continuation character (\) is used to run the string definition onto multiple lines of the file. It is also possible to build this string dynamically, by using the sprintf() calls that are part of the C language.