Implementation & Support Functions

FilterExplorer is a Microsoft® Windows® 32-bit GUI application that will run under Windows® versions later than Windows® 95. The application was written in Microsoft® Visual C++® Version 6.0 Standard Edition using the MFC AppWizard. The AppWizard created a basic Windows® GUI application automatically using MFC for a single document interface. This means that I had to simply write the code to fill that single document, in this case, an image (the single document interface means that only one image can be opened at a time).

The entire FilterExplorer Visual C++® project code may be downloaded from the downloads section.

Using code from [SMAL00] I was easily able to open and save JPEG and BMP images to and from my application. As read from a file, the [SMAL00] code stores the image width and height as unsigned integers and the image as a buffer of bytes. The image buffer can be thought of as a one-dimensional array of bytes, three times the length of the width times the height of the image. A pixel is three bytes long, one byte for each red, green, and blue value. The image buffer could be allocated as follows:

   buf = (BTYE*)new BYTE(height * width * 3);

[SMAL00] gives code to display an image in this form on the screen.

Most of the code written by myself is found in two files, filters.h and filters.cpp. Here I have defined the types: pixel, hsvpixel, and imagematrix, and the classes: CImage and CFilter (actually, pixel and hsvpixel are implemented as classes too). A pixel contains three bytes, one for each red, green, and blue component. Operations have been defined for a pixel to: set the pixel’s color (the RGB components), get the pixel’s intensity, and lighten or darken the pixel. A hsvpixel is not composed of RGB components but rather is defined by Hue, Saturation, and Value (all implemented as type double). Some filters are based on this description of a pixel rather than the native RGB. Conversion algorithms can be found in [FOLE90:592-593], [PCIIHS], and [PCIRGB], and have been implemented here.

An imagematrix is defined as a two dimensional array (using pointer notation):

   typedef pixel** imagematrix;

I have written routines to convert an image to and from an imagematrix and the buffer form of an image described by [SMAL00]. For ease of programming (and readability) manipulation of an image is only done when the image is in imagematrix form. For example, a white pixel would have each RGB component set to 255. Therefore, to set the pixel at coordinate (x,y) in imagematrix img to white one could do the following:

   img[y][x].r = 255;
   img[y][x].g = 255;
   img[y][x].b = 255;

Or, simply call the color(r, g, b) function:

   img[y][x].color(255,255,255);

Or more simply, the overloaded color() function:

   img[y][x].color(255);

which sets each component to 255. Note that in the above example the (x,y) coordinates are reversed. This is simply how the image is stored. (Also, directly from the file, JPEG and BMP images are stored with their bytes in BGR order. Since this somewhat different from our convention [SMAL00] has code to flip the red and blue pixel values.)
The CImage class contains members that describe and manipulate an image in buffer form. The functions here load or save images, display an image on the screen, or call a filter. The function that calls a filter creates a CFilter object of filter type specified by the call and calls CFilter’s member function go() to run the filter:

   void CImage::ProcessFilter(enum filtertype type)
   {
      CFilter currentfilter(this, type);
      currentfilter.go();
   }

Hidden here is the fact that the CFilter constructor converts the buffer form of the image to an imagematrix and sets the filter type. There is one public CFilter function, go(). It takes no arguments, but calls the appropriate filter function depending on its (the CFilter object) type. When go() returns, if successful, the CImage buffer contains the filtered image, and an undo buffer (also a member variable of CImage) is created with a copy of the image before the filter. This allows the application to perform one level of undo by simply replacing the undo buffer with the main buffer when the user requests an undo.

The CFilter class is by far the largest and most interesting in terms of this research. All the code before the CFilter code is ‘implementation only’. It is to be noted that one cannot write a program to filter images without having a means of getting and storing an image in program memory, which is definitely not a trivial task and should be obvious to the reader by now.

Besides functions for each filter implemented herein, CFilter contains the aforementioned functions to convert to and from buffer and imagematrix form. For example, to convert from buffer to imagematrix you must first dynamically allocate memory for the imagematrix:

   imagematrix CFilter::CreateImageMatrix(UINT row, UINT col)
   {
      imagematrix img = NULL;
      img = (imagematrix)malloc(row * sizeof(imagerow));
      if (img == NULL)
         return(NULL);
      for (unsigned int i = 0; i < m_height; i++)
      {
         img[i] = (imagerow)malloc(col * sizeof(pixel));
         if (img[i] == NULL)
         {
            FreeImageMatrix(img, row);
            return(NULL);
         }
      }
      return(img);
   }

and then do the simple conversion

   void CFilter::BufferToMatrix(BYTE* buf, imagematrix img)
   {
      UINT col, row;
      for (row=0; row < m_height; row++)
      {
         for (col=0; col < m_width; col++)
         {
            LPBYTE pRed, pGrn, pBlu;
            pBlu = buf + row * m_width * 3 + col * 3;
            pGrn = buf + row * m_width * 3 + col * 3 + 1;
            pRed = buf + row * m_width * 3 + col * 3 + 2;
            img[row][col].r = *pRed;
            img[row][col].g = *pGrn;
            img[row][col].b = *pBlu;
         }
      }
   }

When finished with the imagematrix, the memory must be deallocated. This is especially important in this application, because for large images, the amount of memory allocated to an imagematrix could be quite large. Calling a few filter routines without cleaning up the allocated memory would quickly use up all the memory available on a system (believe me, I found out the hard way). The deallocation function is shown below:

   void CFilter::FreeImageMatrix(imagematrix img, unsigned int row)
   {
      for (unsigned int i = 0; i < row; i++)
      free(img[i]);
      free(img);
   }

The use of FilterExplorer is fairly intuitive as it works like other Windows® application. Images may be opened and saved from the File menu. When an image is open, it will be displayed on the screen and its name will appear in the title bar. Any filter on the Filter menu can be applied by selecting it from the menu and choosing options in a dialog box (if there are any options). There is one level of undo, also available from the filter menu. In addition to using the menus, the toolbar buttons also may be used. 

The following steps occur after a file has been opened and the user chooses a filter from the filter menu:

· The document’s view class’s (CFilterExplorerView) member variable currentimage (type CImage) has already be initialized with the image displayed on screen (this happens when the file is opened).
· The ProcessFilter() member function of currentimage is called. It takes one parameter, the type (or name) of the filter, which is determined by which filter the user clicked on from the menu.
· ProcessFilter() creates a CFilter object of the type which was passed into the function and calls the object’s member function go().
· The cooresponding filter function (in CFilter) is called.
· The filter function calls any appropriate dialog boxes and waits on user input. When the user presses the OK button, two imagematrix types are created. One imagematix (img) is created from the original image buffer (which is the image displayed on the screen). The other (filt) is blank, it will be the new image created after the filter.
· The filter processes img and creates filt.
· If the user does not cancel before the filter is finished, the current image buffer is copied to the undo buffer, the matrix filt is translated to a buffer, and made current (and displayed on the screen). The intermediate img and filt matrices are destroyed.

There are many implementation issues here that are beyond the scope of this report (for example, the use of a progress indicator when a filter is applied). The interested reader should examine the FilterExplorer project code or see a Visual C++® text such as [KRUG98] or [SPHA99], or the Microsoft® Developer Network (MSDN(TM)) articles found at http://msdn.microsoft.com/.

 
Go to Filter Descriptions
 
Return to top and the table of contents
 


© 2001 Jason Waltman