Beautiful Code [23]
FilterMethodCS, shown in Example 8-2, is a fairly straightforward implementation of the filtering algorithm from Example 8-1, but it has been translated to C# and uses one-dimensional arrays.
Example 8-2. A digital filter algorithm in C#
Code View: Scroll / Show All
1 void FilterMethodCS(byte[] src, byte[] dst, int stride, int bytesPerPixel)
2 {
3 int cBytes = src.Length;
4 int cFilter = filter.Length;
5
6 for (int iDst = 0; iDst < cBytes; iDst++)
7 {
8 double pixelsAccum = 0;
9 double filterAccum = 0;
10
11 for (int iFilter = 0; iFilter < cFilter; iFilter++)
12 {
13 int yFilter = iFilter / cyFilter;
14 int xFilter = iFilter % cxFilter;
15
16 int iSrc = iDst + stride * (yFilter - cyFilter / 2) +
17 bytesPerPixel * (xFilter - cxFilter / 2);
18
19 if (iSrc >= 0 && iSrc < cBytes)
20 {
21 pixelsAccum += filter[iFilter] * src[iSrc];
22 filterAccum += filter[iFilter];
23 }
24 }
25 if (filterAccum != 0)
26 pixelsAccum /= filterAccum;
27
28 dst[iDst] = pixelsAccum < 0 ? (byte)0 : (pixelsAccum > 255 ?
29 (byte)255 : (byte)pixelsAccum);
30 }
31 }
The first two parameters are the source and destination arrays src and dst. The third parameter is stride, which is the number of bytes in each row of the source and destination bitmaps. This stride value is generally equal to the pixel width of the bitmap times the number of bytes per pixel, but for performance reasons it might be rounded up to a four-byte boundary. (Because the program only works with full-color bitmaps, the number of bytes per pixel will always be three or four.) It's not necessary to calculate the stride value because it's provided with the information returned by the LockBits method when you get access to the bitmap bits. The method begins by saving the number of bytes in both the src and filter arrays to avoid frequent accesses of the Length property. The variables that begin with the letter i are indexes to the three arrays used in the method.
If the goal here is to write a fast digital filter algorithm, then FilterMethodCS is a failure. With a 24-bit-per-pixel 300,000-pixel bitmap and a 5 x 5 filter, this method requires about two seconds on my 1.5 GHz Pentium 4. Two seconds might not seem too bad, but a 5 x 5 filter applied to a 32-bit-per-pixel 4.3 megapixel bitmap requires about half a minute, and that is very long. Yet I can't see any way to improve the C# code to make it more efficient.
Traditionally, if a function isn't working fast enough and you don't feel you can optimize it any further, you start considering assembly language. In this era of platform independence and managed code, you might instead consider the vaguely equivalent approach of writing the routine directly in .NET Intermediate Language. This is certainly an entirely plausible solution, and might even be considered fun (to the right type of mentality). However, even coding in Intermediate Language might not be sufficient. Use the IL Disassembler to look at the Intermediate Language generated by the C# compiler for FilterMethodCS. Do you really think you can improve greatly on that?
The real problem with FilterMethodCS is that it's generalized for bitmaps of any dimension and for filters of any dimension. Much of the code in FilterMethodCS is just "busy work" involved with looping and indexing. This method could be improved dramatically if it didn't have to be so generalized. Suppose you were always dealing with 32-bit-per-pixel bitmaps of the same size, which I'll symbolize as CX and CY (think of these uppercase letters as #defines in C or C++, or const values in C#). And suppose you always used the same filter— a 3 x 3 filter with fixed elements whose fields are symbolized like the ones in Figure 8-3.
Figure 8-3. Array layout of a 3x3 filter
How would you write the filter method then? You might decide to dispense with the iFilter loop and just hardcode the logic for the nine filter elements:
Code View: Scroll / Show All
// Filter cell F11
int iSrc = iDst - 4 * CX - 4;
if (iSrc