/////////////////////////////////////////////////////////////////////////////// // Image.cpp // // This image class contains the data required to represent a // graphics image. There are various routines for accessing the image // data, but you may not access the data directly. /////////////////////////////////////////////////////////////////////////////// #include "Image.h" #include // No global variables. /////////////////////////////////////////////////////////////////////////////// // Image::Image () Default Constructor // // This constructor initializes this image to be a 1x1 black image. /////////////////////////////////////////////////////////////////////////////// Image::Image () : data (NULL) { allocateImage (1, 1); } /////////////////////////////////////////////////////////////////////////////// // Image::Image (int width, int height) Constructor // // This constructor initializes this image to be a black image of the // specified width and height. /////////////////////////////////////////////////////////////////////////////// Image::Image (int width, int height) : data (NULL) { allocateImage (width, height); } /////////////////////////////////////////////////////////////////////////////// // Image::Image (const Image & in) Copy Constructor // // This constructor initializes this image to be a copy of the // given image. /////////////////////////////////////////////////////////////////////////////// Image::Image (const Image & in) : data (NULL) { copyImage (in); } /////////////////////////////////////////////////////////////////////////////// // Image::~Image () Default Denstructor // // Free up all memory associated with this object. /////////////////////////////////////////////////////////////////////////////// Image::~Image () { freeImage (); } /////////////////////////////////////////////////////////////////////////////// // Image & Image::operator = (const Image & in) Assignment Operator // // This function makes it so that images can be assigned to other // image variables. /////////////////////////////////////////////////////////////////////////////// Image & Image::operator = (const Image & in) { copyImage (in); return *this; } /////////////////////////////////////////////////////////////////////////////// // Pixel & Image::getPixel (int x, int y) // void Image::setPixel (int x, int y, const Pixel &p) // // These routines are used to access pixels. Attempting to read an // out-of-bounds pixel will result in a bright purple pixel. Attempting // to write an out-of-bounds pixel will do nothing. All other accesses // will behave as expected. // // Pixels are numbered from the upper-left hand corner of the image, // with the uppermost, leftmost pixel being 0, 0. // // Note that pixels are passed by reference. /////////////////////////////////////////////////////////////////////////////// Pixel & Image::getPixel (int x, int y) { static Pixel errorPixel; // Validate parameters and make sure we have an image. If not valid, // return a bright purple pixel. if (data == NULL || x < 0 || x >= imageWidth || y < 0 || y >= imageHeight) { errorPixel.setRGBColor (1, 0, 1); return errorPixel; } // Return the appropriate pixel. return data[y][x]; } void Image::setPixel (int x, int y, const Pixel & p) { // Validate parameters and make sure we have an image. If no image, // or invalid parameters, return early from the function. if (data == NULL || x < 0 || x >= imageWidth || y < 0 || y >= imageHeight) return; // Set the pixel. data[y][x] = p; } /////////////////////////////////////////////////////////////////////////////// // int Image::getWidth () // int Image::getHeight () // // These access functions allow you to access the height and width // of the current image. If for any reason the current image is invalid, // these functions will return 0. /////////////////////////////////////////////////////////////////////////////// int Image::getWidth () { return imageWidth; } int Image::getHeight () { return imageHeight; } /////////////////////////////////////////////////////////////////////////////// // void Image::resize (int width, int height) // // This function destroys the current image, and creates a new image // of the specified dimensions. The new image is black. /////////////////////////////////////////////////////////////////////////////// void Image::resize (int width, int height) { allocateImage (width, height); } /////////////////////////////////////////////////////////////////////////////// // bool Image::writeBMP (char * filename, ostream & errorout) // // This function writes the image to a file using Windows .bmp format. // It is written as a true-color image (24 bit). You may not call this // routine from your compressor class! // // Upon successful completion, this function returns true. If for // any reason this function fails (cannot open file, no image, etc.), then // false is returned. // // Error messages are written to errorout. (errorout may be NULL if // you do not wish to print error messages.) /////////////////////////////////////////////////////////////////////////////// bool Image::writeBMP (char * filename, ostream & errorout) { int temp, x, y, c; // Make sure we have a valid filename and image. if (filename == NULL || data == NULL) { if (errorout != NULL && data == NULL) errorout << "Image::writeBMP - No image to write." << endl; else errorout << "Image::writeBMP - No filename given." << endl; return false; } // Attempt to open the file for writing. ofstream out (filename); if (!out) { if (errorout != NULL && data == NULL) errorout << "Image::writeBMP - Unable to write to file " << filename << "." << endl; return false; } // Rows need to be 32-bit aligned. Calculate how many bytes of padding // are needed. int padding = (4 - ((imageWidth * 3) % 4)) % 4; // Calculate the size of the file. int size = (imageWidth * 3 + padding) * imageHeight + 54; // Write the bitmap file header. Note little-endian values. out.put ('B'); // Required for proper ID. out.put ('M'); // Required for proper ID. temp = size; out.put (temp % 256); temp /= 256; // Size of the file. out.put (temp % 256); temp /= 256; out.put (temp % 256); temp /= 256; out.put (temp); out.put (0); // Required -- reserved. out.put (0); // Required -- reserved. out.put (0); // Required -- reserved. out.put (0); // Required -- reserved. out.put (54); // Offset from file start to pixel data (4 bytes). out.put (0); out.put (0); out.put (0); // Write the bitmap info header. out.put (40); // Size (in bytes) of this info header (4 bytes). out.put (0); out.put (0); out.put (0); temp = imageWidth; out.put (temp % 256); temp /= 256; // Width of the image. out.put (temp % 256); temp /= 256; out.put (temp % 256); temp /= 256; out.put (temp); temp = imageHeight; out.put (temp % 256); temp /= 256; // Height of the image. out.put (temp % 256); temp /= 256; out.put (temp % 256); temp /= 256; out.put (temp); out.put (1); // Number of image planes -- must be 1. out.put (0); out.put (24); // Number of bits per pixel. out.put (0); out.put (0); // Compression used -- none. out.put (0); out.put (0); out.put (0); temp = size - 54; out.put (temp % 256); temp /= 256; // Size (in bytes) of the pixel data. out.put (temp % 256); temp /= 256; out.put (temp % 256); temp /= 256; out.put (temp); out.put (0); // X pixels per meter -- 0 = use default. out.put (0); out.put (0); out.put (0); out.put (0); // Y pixels per meter -- 0 = use default. out.put (0); out.put (0); out.put (0); out.put (0); // Colors used - 0 = all used out.put (0); out.put (0); out.put (0); out.put (0); // Colors important - 0 = all important out.put (0); out.put (0); out.put (0); // Next we would write the RGB quad data structure, but since we are // writing a true color image, we can skip this. // Finally, we need to write the RGB data. Write one RGB triplet for // each pixel, beginning in the lower left hand corner. Write the // data one row at a time, moving from left to right. Pad each row // to end on a 32 bit boundary. for (y = imageHeight; y--;) { for (x = 0; x < imageWidth; x++) { out.put ((int) (data[y][x].getBlue () * 255)); // Blue is first. out.put ((int) (data[y][x].getGreen () * 255)); out.put ((int) (data[y][x].getRed () * 255)); } for (c = 0; c < padding; c++) out.put (0); } // Done, close the stream and return true. out.close (); return true; } /////////////////////////////////////////////////////////////////////////////// // bool Image::readBMP (char * filename, ostream & errorout) // // This function re-initializes the current object by reading an image // from a windows .bmp format file. You may not call this routine // from within your compressor class! // // Upon successful completion, this function returns true. If for // any reason this function fails (cannot open file, no image, etc.), then // false is returned and the image is reset to a 1x1 black image. // // Error messages are written to errorout. (errorout may be NULL if // you do not wish to print error messages.) /////////////////////////////////////////////////////////////////////////////// bool Image::readBMP (char * filename, ostream & errorout) { int size, x, y, c; unsigned char fileheader[14]; // Buffers to contain the file headers unsigned char * infoheader; unsigned char * colortable; // This will point into infoheader unsigned char * pixeldata; // Wipe out any existing image. freeImage (); // Validate the filename. if (filename == NULL) { if (errorout != NULL) errorout << "Image::readBMP - No filename specified." << endl; return false; } // Attempt to open the file for reading. ifstream in (filename); if (!in) { if (errorout != NULL) errorout << "Image::readBMP - Unable to read from " << filename << "." << endl; return false; } // Read the bitmap file header. if (in.read(fileheader, 14).gcount () != 14) { if (errorout != NULL) errorout << "Image::readBMP - Unable to read header from " << filename << "." << endl; in.close(); return false; } // Make sure this header looks legit and extract some info. if (fileheader[0] != 'B' || fileheader[1] != 'M') { if (errorout != NULL) errorout << "Image::readBMP - " << filename << "is not a .bmp." << endl; in.close (); return false; } int filesize = fileheader[2] + fileheader[3] * 256l + fileheader[4] * 65536l + fileheader[5] * 16777216l; int pixeloffset = fileheader[10] + fileheader[11] * 256l + fileheader[12] * 65536l + fileheader[13] * 16777216l; // Now that we know where the pixel data starts, we can read the info // header and color table by reading everything up to the pixel data. // Note that we must allocate this memory. size = pixeloffset - 14; infoheader = new unsigned char [size]; if (infoheader == NULL) { if (errorout != NULL) errorout << "Image::readBMP - Out of memory." << endl; in.close (); return false; } if (in.read (infoheader, size).gcount () != size) { if (errorout != NULL) errorout << "Image::readBMP - Unable to read info from " << filename << "." << endl; in.close (); delete [] infoheader; return false; } // Point to the color table. int infosize = infoheader[0] + infoheader[1] * 256l + infoheader[2] * 65536l + infoheader[3] * 16777216l; if (infosize < size) colortable = &(infoheader[infosize]); else colortable = NULL; // Allocate an array and read all the pixel data. size = filesize - size - 14; pixeldata = new unsigned char [size]; if (pixeldata == NULL) { if (errorout != NULL) errorout << "Image::readBMP - Out of memory." << endl; in.close (); delete [] infoheader; return false; } if (in.read (pixeldata, size).gcount () != size) { if (errorout != NULL) errorout << "Image::readBMP - Unable to read pixels from " << filename << "." << endl; in.close (); delete [] infoheader; delete [] pixeldata; return false; } // We are done with the file, close it. in.close (); // Extract the various fields needed from the info header. if (infosize < 16) { if (errorout != NULL) errorout << "Image::readBMP - " << filename << " - info block too short." << endl; delete [] infoheader; delete [] pixeldata; return false; } int width = infoheader[4] + infoheader[5] * 256l + infoheader[6] * 65536l + infoheader[7] * 16777216l; int height = infoheader[8] + infoheader[9] * 256l + infoheader[10] * 65536l + infoheader[11] * 16777216l; int planes = infoheader[12] + infoheader[13] * 256l; int bitcount = infoheader[14] + infoheader[15] * 256l; int compression = 0; int sizeimage = 0; if (infosize >= 20) { compression = infoheader[16] + infoheader[17] * 256l + infoheader[18] * 65536l + infoheader[19] * 16777216l; } if (infosize >= 24) { sizeimage = infoheader[20] + infoheader[21] * 256l + infoheader[22] * 65536l + infoheader[23] * 16777216l; } // For now, we can only read images stored uncompressed in 24 bit mode. // Ignore all others. int padding = (4 - ((width * 3) % 4)) % 4; // Debugging info. // cout << "padding " << padding << endl; // cout << "width " << width << endl; // cout << "height " << height << endl; // cout << "compression " << compression << endl; // cout << "bitcount " << bitcount << endl; // cout << "size " << size << endl; // cout << "sizeimage " << sizeimage << endl; if (width < 1 || height < 1 || compression != 0 || bitcount != 24 || (sizeimage != 0 && sizeimage != size) || size != (width * 3 + padding) * height) { if (errorout != NULL) errorout << "Image::readBMP - " << filename << " - this .bmp is not true color (24 bit) and/or is not" << " uncompressed data." << endl; delete [] infoheader; delete [] pixeldata; return false; } // Build an empty image buffer. allocateImage (width, height); if (data == NULL) { if (errorout != NULL) errorout << "Image::readBMP - Out of memory." << endl; delete [] infoheader; delete [] pixeldata; return false; } // Initialize the pixels. Note that the bitmap is stored beginning at // the bottom left pixel. for (c = 0, y = height; y--; c += padding) for (x = 0; x < width; x++, c+=3) data[y][x].setRGBColor (pixeldata[c+2] / 255.0, pixeldata[c+1] / 255.0, pixeldata[c] / 255.0); // Done! We've successfully built the image, so clean up memory // and return true. delete [] infoheader; delete [] pixeldata; return true; } /////////////////////////////////////////////////////////////////////////////// // void Image::allocateImage (int width, int height) // // This function allocates the memory needed to hold an image of // the specified size. If there is not enough memory, the image size // is set to 0, 0 and the data pointer is set to NULL. // // By default, the image is black (because Pixel objects default to // black). // // As a safety precaution, this routine may not be used to allocate // any image containing more than 50,000,000 pixels. This will count as // an error. /////////////////////////////////////////////////////////////////////////////// void Image::allocateImage (int width, int height) { // Deallocate the current image. freeImage (); // Validate the parameters. On error, return early. if (width < 1 || height < 1 || width * height > 50000000 || width * height < 0) // In case of overflow return; // Set up the width and height. imageWidth = width; imageHeight = height; // Allocate an array to hold arrays of pixels. On error, free the image // again and return early. data = new Pixel * [height]; if (data == NULL) { freeImage (); return; } // Allocate the rows of pixels for this image. Again, on error, free // the image and return early. (Do it in two steps so that every // array entry has a value.) for (int y = 0; y < height; y++) data[y] = new Pixel [width]; for (int y = 0; y < height; y++) if (data[y] == NULL) { freeImage (); return; } // Done. } /////////////////////////////////////////////////////////////////////////////// // void Image::freeImage () // // This function releases any memory allocated by the allocateImage () // function. The image size is set to 0, 0 and the image pointer is // set to NULL. /////////////////////////////////////////////////////////////////////////////// void Image::freeImage () { // Is there a vaild image? If not, return early. if (data == NULL) { imageWidth = 0; imageHeight = 0; return; } // Loop through the rows of the image, delete each row if it exists. for (int y = 0; y < imageHeight; y++) { if (data[y] != NULL) delete [] data[y]; } // Delete the array of rows. delete [] data; // Clear the image variables. data = NULL; imageWidth = 0; imageHeight = 0; } /////////////////////////////////////////////////////////////////////////////// // void Image::copyImage (const Image & in) // // This function copies the given image into the current image. /////////////////////////////////////////////////////////////////////////////// void Image::copyImage (const Image & in) { // Release any current image. freeImage (); // If the input image is null, bail now because we are done. if (in.data == NULL) return; // Allocate the space for the copy of the image data. allocateImage (in.imageWidth, in.imageHeight); // If our data field is null, we are out of memory. if (data == NULL) return; // Copy the data. for (int y = 0; y < imageHeight; y++) for (int x = 0; x < imageWidth; x++) data[y][x] = in.data[y][x]; }