Beautiful Code [24]
{
pixelsAccum += src[iSrc] * F11;
filterAccum += F11;
}
// Filter cell F12
iSrc = iDst - 4 * CX;
if (iSrc >= 0 && iSrc < 4 * CX * CY)
{
pixelsAccum += src[iSrc] * F12;
filterAccum += F12;
}
// Filter cells F13 through F32
...
// Filter cell F33
iSrc = iDst + 4 * CX + 4;
if (iSrc >= 0 && iSrc < 4 * CX * CY)
{
pixelsAccum += src[iSrc] * F33;
filterAccum += F33;
}
This approach gets rid of the looping logic, simplifies the calculation of iSrc, and eliminates the access of the filter array. Although the code is definitely bulkier, it is guaranteed to be faster. In fact, because you know the values of all the filter elements, you can reduce the code somewhat by eliminating those cases where the filter element is 0, and simplifying those cases where the filter element is 1 or –1.
Of course, hardcoding this logic isn't practical because you really want the ability to deal with many differently sized bitmaps and many types of filters. These properties are not known until it's time to actually apply the filter.
Rather than hardcoding the filter logic, a much better approach would be to generate custom code on the fly based on the size and pixel depth of the bitmap, and the size and elements of the filter. In olden days, you might do as the Windows developers did with BitBlt, which was to generate machine code in memory, and then execute it. Translated to modern times, with our concern for portability, the solution might be to generate .NET Intermediate Language using C#, and then execute it.
This is actually a workable solution. In a C# program you can create a static method in memory that consists of instructions in Intermediate Language, and then execute that method, at which point the .NET just-in-time compiler enters the picture to convert your Intermediate Language to machine code. At no time during this entire process do you stop writing managed code.
The facility to dynamically generate Intermediate Language was introduced in .NET 2.0 and involves classes in the System.Reflection.Emit namespace. You can generate whole classes and even entire assemblies, but for smaller applications (such as the one we're developing), you can simply generate a static method and then call it. This is what I've done in FilterMethodIL in the ImageFilter class.
I'm going to show you all of FilterMethodIL here (but eliminating many of the comments you'll find in the ImageFilter.cs source code file) because it involves some interesting interplay between the C# code and the generated Intermediate Language. Keep in mind throughout this exercise that FilterMethodIL generates this Intermediate Language whenever a specific filter is applied to a specific bitmap, so all aspects of the filter can be hard-coded into the Intermediate Language, as well as the size and pixel depth of the bitmap. Obviously, some overhead is required to generate this code, but that cost is dwarfed by the number of operations required by large bitmaps, which might easily have over a million pixels.
All the code in FilterMethodIL is shown below in sequence, with explanations throughout to describe what's going on and introduce new concepts. FilterMethodIL has the same parameters as FilterMethodCS and begins by obtaining the total byte size of the bitmap:
void FilterMethodIL(byte[] src, byte[] dst, int stride, int bytesPerPixel)
{
int cBytes = src.Length;
To create a static method in code, you create a new object of type DynamicMethod. The second argument to the constructor indicates the method's return type, and the third argument is an array of the method's parameter types. The fourth argument is the class that's creating this method, and is available from the GetType method:
DynamicMethod dynameth = new DynamicMethod("Go", typeof(void),
new Type[] { typeof(byte[]), typeof(byte[]) }, GetType());
As you can see by the third argument to the constructor, the two parameters in this dynamic method are both byte arrays, and these will be the src and dst arrays from Example