The convolution operation may be implemented by building the output image in the accumulation buffer. For each kernel entry G[i][j], translate the input image by (-i, -j) from its original position and then accumulate the translated image using the command glAccumGL_ACCUM, G[i][j](GL_ACCUM, G[i][j]). This translation can be performed by glCopyPixels() but an application may be able to more efficiently redraw the image shifted using glViewport(). width * height translations and accumulations must be performed. Skip clearing the accumulation buffer by using GL_LOAD instead of GL_ACCUM for the first accumulation.
Here is an example of using the accumulation buffer to convolve using a
Sobel filter, commonly used to do edge detection. This filter is used to
find horizontal edges:
A general algorithm for the 2D convolution operation is:
Draw the input image for (j = 0; j < height; j++) { for (i = 0; i < width; i++) { glAccum(GL_ACCUM, G[i][j]*scale); Move or redraw the input image to the left by 1 pixel } Move or redraw the input image to the right by width pixels Move or redraw the input image down by 1 pixel } glAccum(GL_RETURN, 1/scale);scale is a value chosen to ensure that the intermediate results cannot go outside a certain range. In the Sobel filter example, scale = 4. Assuming the input values are in (0..1), scale can be naively computed using the following algorithm:
float minPossible = 0, maxPossible = 1; for (j = 0; j < height; j++) { for (i = 0; i < width; i++) { if (G[i][j] < 0) { minPossible += G[i][j]; } else { maxPossible += G[i][j]; } } } scale = 1.0 / ((-minPossible > maxPossible) ? -minPossible : maxPossible);Since the accumulation buffer has limited precision, more accurate results can be obtained by changing the order of the computation and computing scale accordingly. Additionally, if values in the input image can be constrained to a smaller range, scale can be made larger, which may also give more accurate results.
For separable kernels, convolution can be implemented using width + height image translations and accumulations. A general algorithm is:
Draw the input image for (i = 0; i < width; i++) { glAccum(GL_ACCUM, Grow[i] * rowScale); Move or redraw the input image to the left 1 pixel } glAccum(GL_RETURN, 1 / rowScale); for (j = 0; j < height; j++) { glAccum(GL_ACCUM, Gcol[j] * colScale); Move or redraw the framebuffer image down by 1 pixel } glAccum(GL_RETURN, 1 / colScale);In this example, it is assumed that scales for the row and column filters have been determined in a similar fashion to the general two-dimensional filter, such that the accumulation buffer values will never go out of range.