Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
88 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

  Contents

 Introduction
 Convolution
 Code
 Filters
 Edge Enhancement
 Edge Detection
 Tips and Tricks

 Printable version

 


OK, I've outlined the process, now I'm gonna outline the source code, give you the source code, then explain it. Hang on to your hats (or mice, I guess)!

In my first attempt to use convolution, I used a single one-dimensional array called Filters (char) to store all the kernals I would ever use. The format of the array is simple:

Offset Description
0-8 The first 3x3 matrix
9 The first normalization constant
10 The second 3x3 matrix
19 The second normalization constant

I used this format because it is fairly easy to program and easily portable to assembly language. However, I will stick to C++ for this article. I've also defined quite a few constants into Filters, a code snippit looks like this:

#define AVERAGE 0 #define LP1 10 #define LP2 20 #define LP3 30 #define GAUSSIAN 40...

These are pointers to the various kernals - use them as easy reference. You don't need to remember that LP1 starts at Filters[10], but it is easier to say Filters[LP1] than Filters[10].

The procedure that actually filters the image is deftly named Filter. It filters the picture stored in a Picture struct named Source, and stores the filtered image in a Picture struct named Dest. The pointer into Filters is given in the parameter Filt. Then the fun begins. Memory is allocated for the new picture in Dest. If this memory allocation fails, the procedure returns 1. I assumed a 256-color image (it's easier to program) for this version, but a palette is NOT assigned to Dest - it's exactly the same as Source anyway. The procedure then runs through every pixel in the source image to filter/copy to Dest. I say filter/copy because all pixels on the sides are just copied, while all the other pixels are filtered. In the filtering process, the sum of all the pixels is stored in the long variable named numb, and, if numb isn't zero, it is divided by the normalization constant. If numb is positive, it is copied to Dest, otherwise the pixel in Dest is set to zero. A return value of 0 signals that the filtering succeeded.

I mentioned use of a Picture struct. These are used to store the bitmaps in memory and are coded quite simply. Their structure is like this:

struct Picture { unsigned short x,y; unsigned char *picture; unsigned char *palette; } Pict;

As you can see, the struct contains four definitions inside it. The short variables x and y are to represent the width and height of an image. The pointer to the picture stores the actual bitmap, and the pointer to the palette stores the 256-color palette. This was taken from PICTURE.H, which you can get here and at the end of the article.

The 8-bit filtering procedure can be found in FILTER.H, which you can download here and at the end of the article.

Now I am going to explain FILTER.H section by section.

// Assumes standard and picture header files are already loaded

This line is important - don't overlook it. Don't include this file unless you are including PICTURE.H (you can downlaod this above and at the end of the article). I often use another file of my creation: BMP.H, which loads Windows and OS/2 version Bitmap files. Though it has nothing to do with digital filtering, I have included it here for the example programs. You can download it here and at the end of the document.

Immediately following the above line lies the defines and definitions of Filters. Filters is defined in this fashion:

char Filters[] = { // *****CONVOLUTION/CORRELATION***** // ***LOW PASS FILTERS*** // Average 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, // LP1 1, 1, 1, 1, 2, 1, 1, 1, 1, 10, // LP2 1, 1, 1, 1, 4, 1, 1, 1, 1, 12, ...

As explained before, the first nine bytes are the 3x3 kernal, stored in the order illustrated, followed by the normalization constant. Although the constant can be calculated from the 3x3 kernal rather easily, I included it in the definition for two reasons: speed and simplicity. Speed, for you don't need to calculate it - it's given. Simplicity, for it makes each filter take 10 bytes, which is a great deal easier to deal with in assembly language than 9 bytes (if you're an optimization guru, you'll understand). Here's what follows:

int Filter(Picture &Source,Picture &Dest,unsigned short Filt) { if((Dest.picture=new unsigned char[Source.x*Source.y])==NULL) return 1; // Error allocating memory for Dest picture Dest.x=Source.x; Dest.y=Source.y; signed long numb;

When calling Filter, you need to provide a Source picture, a Dest picture, and an unsigned short named Filt. If you recall, Filt is the pointer into Filters - it is which kernal you want to use. A value of 0 uses the Average filter, a value of 10 uses the LP1 filter, a value of 20 uses the LP2 filter, and so on. The memory is allocated for Dest.picture, the x/y resolution is copied, and numb, which is used to calculate the filtered pixel, is declared. Next:

for(unsigned short x=0;x<Dest.x;x++) for(unsigned short y=0;y<Dest.y;y++) { if((x)&&(y)≈&(x!=Dest.x-1)&&(y!=Dest.y-1)) { numb=(long)((Source.picture[(y-1)*Source.x+(x-1)]*Filters[Filt]+ Source.picture[(y-1)*Source.x+(x)]*Filters[Filt+1]+ Source.picture[(y-1)*Source.x+(x+1)]*Filters[Filt+2]+ Source.picture[(y)*Source.x+(x-1)]*Filters[Filt+3]+ Source.picture[(y)*Source.x+(x)]*Filters[Filt+4]+ Source.picture[(y)*Source.x+(x+1)]*Filters[Filt+5]+ Source.picture[(y+1)*Source.x+(x-1)]*Filters[Filt+6]+ Source.picture[(y+1)*Source.x+(x)]*Filters[Filt+7]+ Source.picture[(y+1)*Source.x+(x+1)]*Filters[Filt+8])); if(numb) numb/=(long)Filters[Filt+9]; if(numb>0) Dest.picture[y*Dest.x+x]=(unsigned char)numb; else Dest.picture[y*Dest.x+x]=0; } else Dest.picture[y*Dest.x+x]=Source.picture[y*Dest.x+x]; } return 0; // No error }

Every pixel in the image is filtered/copied, as already explained, through the two for commands in the beginning. The if command on the third line makes sure that the pixel is not on a side. numb is assigned the sum of all the pixels (as weighted as the kernal declares), divided by the normalization constant (Filters[Filt+9]), and stored in Dest.picture, if it isn't negative. And you're done.




Next : Filters