Alright, I have explained all the normal kernals. Now for some tips and tricks. Here's one: most of the kernals that we've gone through can be rotated. For example - take the sobel horizontal from above and rotate it 45 degrees. The table on the left is the original sobel, the table on the right is the rotated sobel:
Another neat trick is to modify the kernal size. Usually if you want less of an effect, you use a larger kernal. However, there are better uses. In edge enhancement (gradient filters especially), you can actually identify lines and other neat stuff. With a kernal size of 3x3, it is hard to identify lines, for things that aren't supposed to be highlighted are. However, if you extend the kernal into a larger size, it is easier to identify objects - at the cost of more processing time. Here's an example of how to detect a horizontal line:
In fact, it is indeed possible to construct templates which can detect a specific shape in an image. These templates are very large kernals and contain characteristics of the object. The above filter that detects horizontal lines is shown in HORIZ1.CPP. The first screen is the original image, the second is when you pass it through a 3x3 filter, the third is when you pass it through a 7x3 filter, exactly like above. Here is the difference this makes: More advanced digital filtering techniques involve statistical (nonlinear) convolution and using cross convolution, but the above filters should keep you busy for quite a while. Now I'll focus on a "true" filter procedure and optimization. Take a look at NEAT1.CPP. This was my earliest attempt from many years ago to create a graphics primitive library. None of the shape-drawing routines are optimized at all, but it is fast enough for our purposes. Now, the program is key-sensitive: press b' and the program will draw its neat little shapes. Press a', and the program will first pass the image through a filter, then draw the filtered image. Press x' and the program will terminate. So, what I've tried to do is implement a filter through an animation in real-time. It uses FILTER.H from above. It has two main problems. The first is obvious: it's SLOW. This is a major obstacle, for we're talking game programming here: every nanosecond counts! I'll tackle this problem last, since the next problem will require a major rewrite of FILTER.H. The second problem is that FILTER.H uses 8-bit terminology. It assumes the palette is correctly set for digital filtering. This means that if you blur color 50 of an image to color 49, 49 should look pretty close to 50, but not be 50. However, this is 8-bit: every color definition can be extremely different. If you want to blur color 50, and you end up with color 49, and color 49 looks nothing like color 50, you have a problem. There are a number of ways to solve this problem. One is to arrange your palette so that colors 1-10 are similar, colors 11-20 are similar, etc. However, this is impractical and timecomsuming. Let's rip up FILTER.H's 8-bit filtering system and make it 24-bit, where every color is not defined by a palette, but by the color itself. OK, we're gonna make a 24-bit FILTER.H. What does that mean? Well, in a 24-bit color mode, every color is defined by 24-bits, making a possible 16.7 million colors. Yet you don't use a palette with 16.7 million colors in it to draw pictures on the screen. The bits in the 24-bit number describe what the color looks like - you don't need a palette. Essentially, it can be divided into 3 byte (8-bit) color attributes: red, green, and blue. Mixing and matching different amounts of these color attributes will make any color in the visible color spectrum. The 24-bit version is called FILTER2.H, and you can download it here and at the end of the article.
It starts out almost exactly like FILTER.H. The only difference here is that Dest.picture is allocated three times the original amount of memory. This is because we are dealing with 24-bits (3 bytes), not just 8-bits (1 byte) anymore.
Now things get a little interesting. This calculates the first of the three bytes (Red).The above procedure is actually repeated two more times, for green and blue. For green, all the +0's become +1, and for blue, all the +0's become +2. This is because every pixel on the screen takes 3 bytes, one for red, one for green, and one for blue. It is necessary to calculate each part of the color separately. Other than that, it is exactly like FILTER.H. The pointer into Source.picture works in the following way. The pixel pointer (y*Source.x+x) is multiplied by 3 (24-bits, not 8), and an addition is made to separate the different parts of the color (+0=Red, +1=Green, and +2=Blue).
The above occurs if the pixel is on a side. If you remember, pixels on the side of an image are simply copied. This works exactly like FILTER.H, except that this is 24-bit. NEAT3.CPP uses the above filter. Note how SLOW it is. Without the filter, it runs on my computer at about 38.7 frames per second. With the filter, that number quickly shrinks to 5.7 frames per second. That's 7 times slower!!! In haste, I've implemented a few optimizations and stored it in FILTER3.H, which is used in NEAT4.CPP. It isn't the most optimized I could make it, and I made no changes to the unoptimized graphics primitives. Here's what I did.
This sped up the procedure a little. Other possible optimizations would require a major rewrite, and I did not include these in the context of this article. I have also included NEAT5.CPP, which implements all the other filters. Pretty much every letter and number trigger a different filter. Type "x" to exit. I hope this article has been enlightening and that you will have fun with digital filtering in the future. All the topics in this article can be downloaded here in a single download: Thanks for your interest! Ender Wiggin |