2.3 Implementation


This section shows the implementation of both the image class and the thumbnail classes. For brevity in the book, we have omitted many of the comments in the code snippets. We have chosen instead to explain our design choices in a narrative fashion. The full implementation, including complete comments, can be found on the CD-ROM.

2.3.1 Image Class

We define our image class in image.h as follows :

 class apImage { public:   apImage  ();   apImage  (int width, int height);   ~apImage ();   apImage            (const apImage& src);   apImage& operator= (const apImage& src);   void swap (apImage& src);   void          setPixel (int x, int y, unsigned char pixel);   unsigned char getPixel (int x, int y) const;   bool isValid () const { return pixels_ != 0;}   // Tests if the image data exists, and presumably valid.   int width ()  const { return width_;}    // Image width   int height () const { return height_;}   // Image height   class rangeError {};   // Exception class private:   void init    ();   void cleanup ();   // Initialize or cleanup the allocated image data   int width_;              // Image width   int height_;             // Image height 

The implementation for the apImage class is straightforward. We'll start with memory allocation and deallocation.

graphics/dia.gif MEMORY ALLOCATION AND DEALLOCATION

Instead of duplicating code every time the image data is allocated or deleted, we define init() and cleanup() functions.

The init() function uses the new operator to allocate memory for storing image data of size width x height .

The orthogonal function is cleanup() which deletes any memory allocated to the apImage class, as shown here.

 void apImage::cleanup () {   // Put the object back into its original, null state.   delete [] pixels_;   width_  = 0;   height_ = 0;   pixels_ = 0; } void apImage::init () {   // All memory allocation passes through this function.   if (width_ > 0 && height_ > 0)     pixels_ = new unsigned char [width_ * height_]; } 
graphics/star.gif

Create orthogonal functions whenever possible. For example, if your class has an open () function, you should also have a close() function.


graphics/dia.gif CONSTRUCTORS AND DESTRUCTORS

By using init() and cleanup() , our constructors and destructors become very simple.

 apImage::apImage () : width_ (0), height_ (0), pixels_ (0) {} apImage::apImage (int width, int height) : width_ (width), height_ (height), pixels_ (0) {   init (); } apImage::~apImage () {   cleanup (); } 
graphics/star.gif

Constructors should initialize all data members to a default state.


The default constructor for apImage creates a null image which is an image with no memory allocation and zero width and height. Null images are very useful objects, and are necessary because some image processing operations can legitimately be null. For example, image processing routines often operate on a particular set of pixels present in multiple images. If there is no overlap in pixels among the images, then a null image is returned. A null image is also returned if an error occurs during an image processing operation. The isValid() member function tests whether an image is null as shown here.

 apImage input; ... apImage output = input.arbitraryAlgorithm (args); if (!output.isValid()) {   // Error processing. arbitraryAlgorithm failed. } 

graphics/dia.gif ASSIGNMENT OPERATOR AND COPY CONSTRUCTOR

We define an assignment operator and copy constructor, because the one that the compiler automatically generates performs a member-wise copy of the class data, which incorrectly copies the image data pointer pixels_ . So, we use the standard C library memcpy () function to duplicate the image data:

 apImage::apImage (const apImage& src) : width_ (0), height_ (0), pixels_ (0) {   if (src.isValid()) {     width_  = src.width ();     height_ = src.height ();     init ();     memcpy (pixels_, src.pixels_, width_ * height_);   } } apImage& apImage::operator= (const apImage& src) {   if (&src != this) {     // Delete any existing data and recreate the source image     cleanup ();     width_  = src.width ();     height_ = src.height ();     init ();     memcpy (pixels_, src.pixels_, width_ * height_);   }   return *this; } 
graphics/star.gif

Use the version of memcpy() or std::copy() supplied on your system. It will be difficult to write a faster version.


As shown, our copy constructor and assignment operator use much of the same code. One way to eliminate this duplication is to use Sutter's technique for writing safe assignment operators. See [Sutter00]. In a nutshell , his technique requires that you define the assignment operator by calling the copy constructor. In addition, his technique requires that you define a swap() function, which switches the data of two apImage objects as shown here.

 template<class T> void swap (T& a, T& b) {   T copy(a);   a = b;   b = copy; } void apImage::swap (apImage& src) {   ::swap (width_,  src.width_);   ::swap (height_, src.height_);   ::swap (pixels_, src.pixels_); } apImage& apImage::operator= (const apImage& src) {   apImage temp (src);   swap (temp);   return *this; } 

The swap<> template function simply exchanges the data of two objects of the same type. It allows us to implement a swap() method for the apImage class that exchanges all of the data in the object with another apImage object. The assignment operator uses the copy constructor to create a copy of src , which is then exchanged with the existing data members of the object. When the assignment operator returns, the temporary apImage object is automatically destroyed . This behavior is especially useful because it guarantees that if any exceptions are thrown, the object will not be left in a partially constructed state.

graphics/dia.gif READING AND WRITING IMAGE PIXELS

Our apImage class is complete once we define the functions that read and write image pixels. Both will throw rangeError if the coordinates do not specify a pixel within the image, or if the image is null.

 void apImage::setPixel (int x, int y, unsigned char pixel) {   if (x < 0  y < 0        x >= width_  y >= height_        !isValid())     throw rangeError ();   unsigned char* p = pixels_ + y*width_ + x;   *p = pixel; } unsigned char apImage::getPixel (int x, int y) const {   if (x < 0  y < 0        x >= width_  y >= height_        !isValid())     throw rangeError ();   // Image data is stored a row at a time.   unsigned char* p = pixels_ + y*width_ + x;   return *p; } 

2.3.2 Thumbnail Class

Now we are ready to define the thumbnail image class, apThumbNail , which computes the thumbnail image from an input image. As with apImage , we initially keep the definition in apThumbnail.h as simple as possible, as shown here.

 class apThumbNail { public:   apThumbNail ();   ~apThumbNail ();   // The default copy constructor and assignment operator are ok   void createThumbNail (const char* inputFile,                         const char* outputFile, int factor);   class invalid {};   // Exception class private:   void readImage  (const char* inputFile);   void writeImage (const char* outputFile) const;   unsigned char averagePixels (int x0, int y0, int factor);   apImage image_;         // input image   apImage thumbnail_;     // thumbnail image }; 

In this class, we considered whether a copy/assignment operator is necessary, but decided it is not. Because it is a common oversight to not define a copy/assignment operator, we explicitly include a comment to let others know that this is a deliberate choice.

graphics/star.gif

If the default copy constructor and assignment operator are acceptable, add a comment stating this to your class declaration.


graphics/dia.gif CONSTRUCTOR AND DESTRUCTOR

Let's discuss the implementation of the thumbnail class, starting with the constructor and destructor.

 apThumbNail::apThumbNail () {} apThumbNail::~apThumbNail () {} 

Although they are both empty, they do serve a purpose. A common mistake during development is to add a new data member to an object and forget to initialize it in the constructor. Defining a constructor makes it more likely that you will remember to initialize new data members.

In addition, you may wonder why these trivial functions are not placed in the header file as an inline definition. Keeping these definitions in the source file is appropriate, because the constructor and destructor are apt to be modified frequently during development and maintenance. We have also found that on some platforms, especially embedded, cross-compiled platforms, inlined constructors cause the memory footprint of an application to increase, because the compiler adds additional housekeeping code. This isn't a problem if the object is referenced only a few times in the code, but, if used frequently, it can dramatically increase the memory footprint.

graphics/star.gif

Except in very trivial objects, never define a constructor or destructor in the header file. These functions are apt to change frequently and inlining these definitions can increase the memory footprint on some platforms.


graphics/dia.gif R EADING AND W RITING I MAGES

Let's skip ahead to the implementation of the readImage() and writeImage() functions. These functions are designed to read an image from a file and convert it into an apImage class, or to write an apImage to a file, respectively. There are numerous file formats designed to store image data, and we deal with them later. For now, we simulate these functions to quickly verify that the design is working properly, before getting bogged down in the details of supporting numerous file formats. readImage() creates an apImage and populates it with image pixels, and writeImage() simply displays the apImage . readImage() also demonstrates how we catch the rangeError thrown by apImage and rethrow it as an invalid error.

 void apThumbNail::readImage  (const char* /*inputFile*/) {   // Create a synthetic 64x64 image   image_ = apImage (64, 64);   try {     for (int y=0; y<image_.height(); y++)       for (int x=0; x<image_.width(); x++)         image_.setPixel (x, y, (unsigned char)(y % 255));   }   catch (apImage::rangeError) {     throw invalid ();   } } void apThumbNail::writeImage  (const char* /*outputFile*/) const {   // Save formatting state of stream   std::ios_base::fmtflags flags = std::cout.flags (std::cout.hex);   int width = std::cout.width (2);   for (int y=0; y< thumbnail_.height(); y++) {     for (int x=0; x< thumbnail_.width(); x++)       std::cout << (int) thumbnail_.getPixel (x, y) << " ";     std::cout << std::endl;   }   std::cout.flags (flags);   std::cout.width (width); } 

graphics/dia.gif CREATING THUMBNAIL IMAGES

With that out of the way, we can look at the function that actually does the work. createThumbNail() takes the name of an input file, the name of the output thumbnail file to create, and how much reduction is desired. Once the input image is read and the thumbnail image is allocated, the function loops through each pixel in the thumbnail image and computes its value:

 void apThumbNail::createThumbNail (const char* inputFile,                                    const char* outputFile,                                    int factor) {   // Validate the arguments   if (inputFile == 0  outputFile == 0        factor <= 1)     throw invalid ();   // Read the source image   readImage (inputFile);   if (!image_.isValid())     throw invalid ();   // Create our internal thumbnail image   thumbnail_ = apImage (image_.width()  / factor,                         image_.height() / factor);   // Turn any rangeErrors from apImage into our invalid error   unsigned char pixel;   try {     for (int y=0; y<thumbnail_.height(); y++) {       for (int x=0; x<thumbnail_.width(); x++) {         // Convert to image_ coordinates to find the average         pixel = averagePixels (x*factor, y*factor, factor);         thumbnail_.setPixel (x, y, pixel);       }     }   }   catch (apImage::rangeError) {     throw invalid ();   }   writeImage (outputFile); } 

createThumbNail() calls averagePixels() to compute the average of pixels in the input image needed to compute a single pixel in the thumbnail image. To be precise, the pixels in a small factor x factor subset of image_ are summed and averaged as shown:

 unsigned char apThumbNail::averagePixels (int x0, int y0,                                           int factor) {   int sum = 0;   // Average factor x factor pixels in the input image   try {     for (int y=0; y<factor; y++) {       for (int x=0; x<factor; x++)         sum += image_.getPixel (x+x0, y+y0);     }   }   catch (apImage::rangeError) {     throw invalid ();   }   // This cast (an int to an unsigned char) is very safe   return static_cast<unsigned char>(sum / (factor * factor)); } 

Although apThumbNail is a simple class, it can also be confusing, because it contains two images with different coordinate systems. In our example, the input image has dimensions width x height , while the thumbnail image has dimensions width/factor x height/factor . Coding mistakes can be made when these coordinate systems are accidentally swapped or blended together. Even though the getPixel() and setPixel() methods in apImage will throw an error if the coordinates are out of bounds, these problems will not be found until a unit test is written (or worse , if there is no unit test, until the integration and test stage of the project).

graphics/star.gif

Functions should not mix or blend coordinate systems, because this greatly increases the chances of introducing bugs .


To decrease the likelihood of bugs, the coordinates used in averagePixels() are all in terms of the input image, image_ . Likewise, the coordinates used in createThumbNail() are mostly in terms of the thumbnail image.

The following line creates the thumbnail image, given the dimensions of the input image, and is written to be self-documenting :

 thumbnail_ = apImage (image_.width()  / factor,                       image_.height() / factor); 

The line that computes the average isn't as obvious, so we add a comment:

 // Convert to image_ coordinates to find the average pixel = averagePixels (x*factor, y*factor, factor); 


Applied C++
Applied C++: Practical Techniques for Building Better Software
ISBN: 0321108949
EAN: 2147483647
Year: 2003
Pages: 59

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net