The next kernel we’re going to look at does a one-dimensional box blur. Here it is:
kernel BoxBlur1D : public ImageComputationKernel<eComponentWise>
{
Image<eRead, eAccessRanged1D, eEdgeClamped> src;
Image<eWrite, eAccessPoint> dst;
param:
int radius; //The radius of our box blur
local:
int _filterWidth;
void define() {
//RIP node will identify radius as the apron
defineParam(radius, "Radius", 5);
}
void init() {
//Set the range we need to access from the source
src.setRange(-radius, radius);
//Set the axis for the 1D-range to be horizontal
src.setAxis(eX);
_filterWidth = 2 * radius + 1;
}
void process() {
float sum = 0.0f;
for(int i = -radius; i <= radius; i++)
sum += src(i);
dst() = sum / (float)_filterWidth;
}
};
This kernel needs to access a one-dimensional range of pixels from its source image. The source image is specified as follows:
Image<eRead, eAccessRanged1D, eEdgeClamped> src;
Here, the eAccessRanged1D tells us that for every output position, the BoxBlur1DKernel needs to access a one-dimensional range of positions from its input, src. The eEdgeClamped says what we want to happen if part of that 1D range falls outside the input image - in this case, we wish the edge pixel to be repeated indefinitely. The alternatives to eEdgeClamped would be eEdgeConstant, which will return zero for all components outside the image, or eEdgeNone. eEdgeNone is the default edge method and means that the kernel will not check whether your access position is outside the image; this can make image access faster if you know you are not going to access outside the image, but is not recommended if you are, as it means the behaviour there will be undefined and it might well lead to a crash.
It’s also possible to have 1D-ranged access to your output image, to allow you to write to a range of positions around each output position. For example, you might have an algorithm which “stamps” out a different filter shape at every output position and accumulates the results, such as a depth-based blur. This would also be specified with eEdgeRanged1D, but you would not specify an edge method in this case; instead, you should be careful not to write outside the image bounds, as that could cause all sorts of problems. However, for our simple 1D box blur we only need to write to the current access position, so we have point access to the dst image, as shown.
Image<eWrite, eAccessPoint> dst;
Now we’ve told the compiler that we want 1D-ranged access to the src image, we need to give it some more information about the extent and direction of the range. This is done in the init() function. First, setRange(...) is called to define the extent of the range around the current output position.
//Set the range we need to access from the source
src.setRange(-radius, radius);
Here, the range depends on the value of the radius parameter, which is our blur size. The range is defined relative to the current position, which we’ll refer to as (x, y). The range passed to setRange(...) is taken to be inclusive on both sides. The range is not fully defined yet though - we also need to specify which axis we want our 1D range to be on. This is done with a call to setAxis(...):
src.setAxis(eX);
In this case we want the blur to be horizontal, so have set the axis to eX. This now fully defines our range, which will allow use to access positions from (x - radius, y) to (x + radius, y) inclusive.
For a vertical range, you would set the axis to eY, and set the range in exactly the same way as before.
The final thing to note is how we access positions inside the 1D range within the process() function. With point access to the input, as in the previous example, the input image src was accessed as src(). In this case, when accessing the src we now have a choice of access positions which could be anywhere inside our 1D range, so we need to specify an offset from the output position.
This process function loops over our horizontal range from left to right, accumulating contributions to our box blur:
for(int i = -radius; i <= radius; i++)
sum += src(i);
dst() = sum / (float)_filterWidth;
In the final step, the output pixel is set to the average value of all the contributions, using the value of _filterWidth we stored inside the init() function.
To turn this from a horizontal to a vertical blur kernel, all you would need to do would be to change the setRange(eX) call inside the init() function to be setRange(eY).
Next we’ll have a quick look at a two-dimensional version of the box blur kernel, which accesses a 2D range from its input.