Beautiful Code [21]
Simple digital image filters are often implemented as small arrays of numbers. These arrays are usually square and have an odd number of rows and columns. Figure 8-1 shows a simple example.
Figure 8-1. Simple digital image filter
Figure 8-1 is a 3 x 3 filter, and you can think of it as transforming a source bitmap into a destination bitmap. For each pixel of the source bitmap, align this filter so its center is over the desired pixel and the other eight cells are aligned with the surrounding pixels. Multiply the nine values in the filter by the nine source pixels, and add up the results. That's the corresponding pixel for the destination bitmap. If the pixels encode color or transparency, you'll want to apply the filter to each color channel separately. Some filters have different arrays for the different color channels or are implemented with algorithms, but we'll stick to the really simple ones for this exercise.
A filter with the value 1/9 in all its cells is a blur filter. Each pixel in the destination bitmap is an average of nine adjacent pixels in the source bitmap. It's convenient that the numbers add up to 1 so the image doesn't get any brighter or darker, but this filter could easily contain all 1s or any other number. All that would be necessary to compensate would be to divide the sum of the products by the sum of the filter cells (as will become apparent, I actually prefer that method).
Figure 8-2 shows a sharpness filter. This filter tends to highlight areas of high contrast.
Figure 8-2. Sharpness filter
Let's suppose we're dealing with gray-shaded bitmaps with one byte per pixel. The pixels of the source bitmap are stored in a two-dimensional array named S. Our job is to calculate the pixels of the destination array named D. The horizontal and vertical sizes of both arrays are stored in the variables cxBitmap and cyBitmap. The Filter is a two-dimensional array named F with dimensions cxFilter and cyFilter. Example 8-1 shows some simple C code for applying the filter.
Example 8-1. Naïve C code to apply a digital filter
Code View: Scroll / Show All
for (yDestination = 0; yDestination < cyBitmap; yDestination++)
for (xDestination = 0; xDestination < cxBitmap; xDestination++)
{
double pixelsAccum = 0;
double filterAccum = 0;
for (yFilter = 0; yFilter < cyFilter; yFilter++)
for (xFilter = 0; xFilter < cxFilter; xFilter++)
{
int ySource = yDestination + yFilter - cyFilter / 2;
int xSource = xDestination + xFilter - cxFilter / 2;
(if ySource >= 0 && ySource < cyBitmap &&
xSource >= 0 && xSource < cxBitmap)
{
pixelsAccum += F[y][x] * S[y][x];
filterAccum += F[y][x];
}
}
if (filterAccum != 0)
pixelsAccum /= filterAccum;
if (pixelsAccum < 0)
D[y][x] = 0;
else if (pixelsAccum > 255)
D[y][x] = 255;
else
D[y][x] = (unsigned char) pixelsAccum;
}
Notice that looping through the filter cells results in accumulating two totals. The pixelsAccum variable is a sum of the products of the source bitmap values and the filter cells, while filterAccum is a sum of the filter cells only. For destination pixels around the edges of the bitmap, some cells of the filter correspond to pixels outside the extent of the source bitmap. I prefer to ignore those cells by adding nothing to pixelsAccum and filterAccum, but to later divide pixelsAccum by filterAccum so that the destination pixel is approximately correct. That's why filterAccum isn't calculated outside the loop and why the filter cells don't have to be normalized to add up to one. Also notice toward the end of this code that the ratio of pixelsAccum to filterAccum has to be clamped between 0 and 255 so no strange effects result.
For every pixel of the destination bitmap, both the source bitmap and the filter must be accessed nine times. Moreover, as the resolution of bitmaps gets higher, filters must often get larger as well to have a noticeable effect on the image.
It's a lot of processing for a high-level language, but I was curious to discover how C# and .NET would fare in handling the pressure. For my experimentation