Image Regions and Infinite Space

Images in RAYZ exist in an infinite 2D plane. Their position in that plane is given by an XY pair, called the offset. By default, images have an offset of (0, 0). This means that images of different sizes will all meet at their lower left hand corner (see illustration).

In order to move an image anywhere within the infinite plane, you simply change the offset. The Shift.C plugin example shows this - it takes an offset value from the user and modifies the input image's context by that offset. The result is that the image moves in X without any actual programmer effort.

Much of the time, multiple inputs to a node are the same size and have the same offset of (0, 0) - thus they line up pixel for pixel. However, it is possible, and easy, to have images which do not line up like this. One common case is when one input is not the same size as the other. Or they may be the same size, but have different offsets. Finally, they may differ in both size and offset.

So what happens when the inputs do not match in size? Take this simple example of an image operation that adds its two inputs together:

    for ( y = result->myContext.offsetY; y < endY; y++ )
    {
        pinA = (T *)cpiGetLine( &inputs[0], y );
        pinB = (T *)cpiGetLine( &inputs[1], y );
        pout = (T *)cpiGetLine( result, y );
        for ( x = result->myContext.offsetX; x < endX; x++ )
        {
            // do the math
            for ( i = 0; i < chans; i++ )
            {
                *pout = (T) (*pinA + *pinB);
                pinA++;
                pinB++;
                pout++;
            }
        }
    }

Say we have two input images defined as follows:

If these two images are plugged into this node in the above order, the result will look like this:

If they are connected in the other order, with image B first then image A, the result will be this:

This is because RAYZ will, by default, pass in to the Process() function just those pixels which exist in both (or all) inputs. Notice that the output size of the node is the same as the first input - the first input to all RAYZ nodes is used as the reference input - it sets the size and bit depth of the resulting image. So in the first instance, above, where the larger image was the first input, the result is an image of that size, which has all the non-coincident pixels set to black (strictly speaking, the pixels are set to the Backing Color). This is different from the second situation, where the output size is the smaller size, and there are no other pixels. You should pause at this point and make sure you understand the difference before moving on. By turning on the "Full Size" overlay in the image viewer, the above ideas are easy to see.

So, given the first situation - a large output with some chunk not being processed - how do we deal with those other pixels? RAYZ allows us to choose how to handle them. We can:

The first one (ignoring the pixels) just happens automatically, so we'll ignore that case. For the next two cases, let's look at an example.

To make this work, we need to define a routine for Control Processing - in the source example Regions.C, you will find the following subroutine and ImageOp structure:


///////////////////////////////////////////


static RPI_ImageOpInfo opinfo =
{
    {
        "regions",          // command name
        "",                 // not used
        "Silicon Grail",    // author
        "0.1"               // version
    },
    RegionsProcess,         // image execution process
    NULL,                   // InputRegion
    NULL,                   // OutputRegion
    RegionsControlProcessing,    // handle regions
    NULL,                   // setup processing
    NULL,                   // cleanup processing
    2,                      // minimum inputs req'd
    CPI_TRUE,               // can this plugin multiproc?
    CPI_FALSE,              // supports disjoint regions?
};  

//////////////////////////////////////////
// The Control Processing function is used to put a top layer of
// compute control on your Image Operation. If this routine is
// defined, then control begins here, and it is up to this routine
// to compute the image(s) properly - in this example, we just
// do that by calling cpiCompute(), but you could call your
// Process function directly, or even not have a process function
// and just do all the work here.
// In this example, we use this routine to find the intersection
// of the two inputs, and then process the region(s) OUTSIDE that
// intersection.

CPI_Bool
RegionsControlProcessing( CPI_Image     *result,
                          CPI_Image      inputs[],
                          CPI_Uint32     numinputs,
                          CPI_Metadata   myParms )
{
    CPI_ImageContext    shared;
    CPI_Bool            color;
    CPI_Bool            retval = CPI_FALSE;

    if ( numinputs == 2 )
    {
        // find the intersection of two image regions
        if ( cpiIntersectRegions( inputs[0].myContext,
                                  inputs[1].myContext,
                                  &shared ) )
        {
            cpiGetMetaBool( myParms, "color", &color );

            // if flag set, color outside area red
            // Magic: puts the result into *result arg for you
            if ( color )
                retval = cpiSetColorOutsideRegion( &shared,
                                                   1.0,
                                                   0.0,
                                                   0.0,
                                                   0.0 );
            else
                // Magic: puts the result into *result arg for you
                retval = cpiCopyOutsideRegion( &shared, &inputs[0] );
        }

        // this kicks off the processing that is defined by
        // the Process function, if any
        cpiCompute( true );
    }

    return retval;
}
This example uses one of two library routines, depending on whether a toggle is set in the node parameter area. If the toggle is not set, the pixels in inputs[0] which are outside of the region shared by the two inputs, are simply copied through.

If the toggle is set, then the outside region is set to a color, in this case red (that is, [1, 0, 0, 0]).

What is happening here is that ControlProcessing() is called first, before any other computation routine. In this routine, we determine the image context of the region which is common to both inputs, and then handle those pixels which are outside that region. Then cpiCompute() is called, and this goes off and calls the Process() function as normal, to compute the common pixels.

Since the ControlProcessing function is called first, it is possible to simply do all the work in it, and not bother to define a Process function, if that works better in your particular case.

There are other utility functions which can be used in these situations. These are listed in CPI_Util.h, and also below:
(Note: The routines marked as "magical" below are magical because they put the result of their activities into the location referenced by the *result pointer which is passed in to the ControlProcessing() function. This just happens automatically.)

cpiCompute( CPI_Bool horizontal )
Kicks off the processing of the IM, including calling the Process() function
cpiIntersectRegions( const CPI_ImageContext  one,
                     const CPI_ImageContext  two,
                     CPI_ImageContext       *result )
	
Returns the region in common between the two passed in contexts
cpiMergeRegions( const CPI_ImageContext  one,
                 const CPI_ImageContext  two,
                 CPI_ImageContext       *result )
Merges the two regions into one new one
cpiCopyRegion( CPI_ImageContext *copyRegion,
               const CPI_Image  *from )
Copies the region given by copyRegion of the 'from' image. Note: this is magical (see above)
cpiCopyOutsideRegion( CPI_ImageContext *skipRegion,
                      const CPI_Image  *from )
Copies the pixels from the 'from' image which are outside the skipRegion area Note: this is magical (see above)
cpiSetColorOutsideRegion( CPI_ImageContext *skipRegion,
                          CPI_Float32       r,
                          CPI_Float32       g,
                          CPI_Float32       b,
                          CPI_Float32       a )
Sets the region of *result which is outside of the skipRegion to the given color Note: this is magical (see above)
cpiSetColorInsideRegion( CPI_ImageContext *fillRegion,
                         CPI_Float32       r,
                         CPI_Float32       g,
                         CPI_Float32       b,
                         CPI_Float32       a )
Sets the region of *result which is inside the skipRegion to the given color Note: this is magical (see above)


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

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