2D Ranged Access

The BoxBlur2DKernel

The next kernel we’re going to look at also does a box blur, but this time in two dimensions. Here it is:

kernel BoxBlur2D : public ImageComputationKernel<eComponentWise>
{
  Image<eRead, eAccessRanged2D, eEdgeClamped> src;
  Image<eWrite, eAccessPoint> dst;

param:
  int xRadius;  //The horizontal radius of our box blur
  int yRadius;  //The vertical radius of our box blur

local:
  int _filterSize;

  void define() {
    defineParam(xRadius, "RadiusX", 5, eParamProxyScale);
    defineParam(yRadius, "RadiusY", 3, eParamProxyScale);
  }

  void init() {
    //Set the range we need to access from the source 
    src.setRange(-xRadius, -yRadius, xRadius, yRadius);

    _filterSize = (2 * xRadius + 1) * (2 * yRadius + 1);
  }

  void process() {
    float sum = 0.0f;
    for(int j = -yRadius; j <= yRadius; j++)
      for(int i = -xRadius; i <= xRadius; i++)
        sum += src(i, j);
    dst() = sum / (float)_filterSize;
  }
};

2D Ranged Access

This kernel needs to access a two-dimensional range of pixels from its source image. The source image is specified as follows:

  Image<eRead, eAccessRanged2D, eEdgeClamped> src;

Here, the eAccessRanged2D tells us that for every output position, the BoxBlur2DKernel needs to access a two-dimensional range of positions from its input, src. As with 1D ranged access, the eEdgeClamped specifies what we want to happen if part of that 2D range falls outside the input image. Alternatively we could have specified eEdgeConstant or eEdgeNone for the edge method here, as before.

You could also write to a 2D range of the output image by specifying eAccessRanged2D access on it, though it’s not required for this example.

Setting up the 2D Range

To set up the 2D range, we again call setRange() inside the init() function. This time, we give it four parameters to specify the horizontal minimum, vertical minimum, horizontal maximum and vertical maximum respectively:

  void init() {
    //Set the range we need to access from the source 
    src.setRange(-xRadius, -yRadius, xRadius, yRadius);

If the range is the same for each dimension, (i.e. xRadius == yRadius) then the shorthand setRange function with two parameters may be used:

src.setRange(-radius, radius)

All ranges are taken to be inclusive on both sides.

Since we’re now using a 2D range which covers both axes, there’s no need to call setAxis() this time.

Accessing the 2D Range

Access to our 2D range from inside the proccess() function is very similar to accessing the 1D range, except that we now need to specify two offsets from the current position instead of one.

This process() function loops over the vertical range of our box blur. For each vertical position, it loops over the horizontal range, accumulating contributions to our box blur from each source position in turn:

  void process() {
    float sum = 0.0f;
    for(int j = -yRadius; j <= yRadius; j++)
      for(int i = -xRadius; i <= xRadius; i++)
        sum += src(i, j);
    dst() = sum / (float)_filterSize;
  }

In the final step, the output pixel is set to the average value of all the contributions, using the value of _filterSize we calculated inside the init() function.

Next Steps

The final type of image access is random access, which we’ll cover next.