In this example, we use a rolling kernel to implement a simple, one-dimensional box blur.
Instead of summing all the values inside the box at every point, we can use the rolling data to store the sum from the previous position. Then we can simply shift the box along by subtracting the value at the back of the box and adding a new value in front, to move it to the next position.
// First, we define a struct to hold the data we want to roll from one pixel to the next.
struct RollingData
{
RollingData()
: sum(0.0f)
{}
// This struct contains a single, floating-point variable.
float sum;
};
// This RollingBoxBlurKernel derives from ImageRollingKernel. The first template argument to ImageRollingKernel
// tells us that this kernel can be run component-wise, as the blurred value of each component does not
// depend on any of the other components. The second template argument to ImageRollingKernel is the data structure
// to be carried from one position to the next, in this case the RollingData we defined above.
kernel RollingBoxBlurKernel : ImageRollingKernel<eComponentWise, RollingData>
{
// The RollingBoxBlurKernel requires read access to a one-dimensional range from its input, src.
// Reads outside the src image will be clamped to the edge value, as is usual for a blur.
Image<eRead, eAccessRanged1D, eEdgeClamped> src;
// We also require write access to the output image, dst.
Image<eWrite> dst;
param:
// This box blur can be run horizontally or vertically. The RollingData will be carried along rows in the
// horizontal case, and down columns in the vertical case.
bool horizontal;
// The radius of this box blur can also be changed. For larger radii, this rolling version of a box blur
// can be faster than the naive version, in which all values inside the box are added up at every location.
int radius;
local:
// The inverse of the box's width is stored here and used to normalise the summed values to get the blurred result
// at each point.
float _filterWidthInv;
void define()
{
// The "horizontal" parameter is given a default of true.
defineParam(horizontal, "horizontal", true);
// The "radius" is given a default value of 10 pixels, which equates to a 21-pixel-wide box.
defineParam(radius, "radius", 10);
}
void init()
{
// An ImageRollingKernel has a run-up which is performed before the start of each roll across the image (i.e.
// along a row or down a column), in order to initialise the data. In this case, we need to accumulate values
// into our "sum" variable before the rolling can begin. The rolling run-up is exclusive at the upper end, so
// here the run-up will be performed at each position in the range [-radius - 1, radius - 1].
setRunupMin(-radius - 1);
setRunupMax(radius);
// Here, the inverse of the box's width is stored and will be used later to normalise the blurred values.
_filterWidthInv = 1.0f / (2 * radius + 1);
// An ImageRollingKernel can be rolled horizontally or vertically. This is defined by setting the "rolling
// axis for the kernel. Here, the axis to roll along will depend on the value of the "horizontal" parameter.
Axis axis = horizontal ? eX : eY;
setRollingAxis(axis);
// We also need to set up access to the input image, src. At each point, we need to be able to read from
// positions in the range [-radius - 1, radius] (this range is inclusive on both sides).
src.setRange(-radius - 1, radius);
// We also need to set the axis for the one-dimensional ranged access to src. This will be the same as the
// axis we are rolling along.
src.setAxis(axis);
}
// The "rollingRunup" function is called once at every position in the run-up region. For example, if we are
// rolling along a row from position 0, this run-up function will be called at every position in the range
// [-radius - 1, radius -1], before the "process" function is called for the first time at position 0.
void rollingRunup()
{
// This function defines what to do inside the run-up region. Here, we want to accumulate values into
// our "sum" variable inside the RollingData struct.
sum += src(0);
}
// After the run-up, the process function will be called at every position in the remainder of the roll
// in turn, e.g. this might be from the start of a row to the end in the horizontal case.
void process()
{
// At each position, we want to drop one value from the back of the box and add a new one on the front,
// thereby shifting the box along by one position. We drop the value at (radius - 1) and add the value
// at radius, so our "sum" variable now contains the sum of the values in the range [radius, radius].
sum += src(radius) - src(-radius - 1);
// Finally, we multiply the sum by our normalisation factor, _filterWidthInv, to get the blurred value
// for the current position, and write it to the output image.
dst() = sum * _filterWidthInv;
}
};