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