////////////////////////////////////////////////////////////////////////////// // hseval.cpp // // This utility uses the Image and Compressor classes to compress a // true color BMP file, decompress the image, and evaluate how closely // the final image matches the source image. // // Scoring an image is based on image size as well as image quality. // The formula is: score = 100 * (quality percentage - compressed percentage). // Compressed percentage is: Compressed image size / original BMP size // Quality percentage is evauated functionally and is dependent on many // different things. /////////////////////////////////////////////////////////////////////////////// #include "Compressor.h" #include #include #include #include /* This structure is used in the analysis procedure to pass results through recursive function calls. */ struct Result { double area; // The number of pixels this result covers. double score; // The score for the given area. double dupAvgH; // Color averages for the given area. double dupAvgS; double dupAvgV; double masterAvgH; double masterAvgS; double masterAvgV; // Initialize every instance to 0 using a constructor. Result () : area(0), score(0), dupAvgH(0), dupAvgS(0), dupAvgV(0), masterAvgH(0), masterAvgS(0), masterAvgV(0) {}; }; /* This function adjusts the score in a result structure using a hacked evaluation of the HSV color averages. */ void adjustScore (Result & data) { double hScore, sScore, vScore; double deltaH, deltaS, deltaV; double sum; // Calculate differences. deltaH = fabs (data.dupAvgH - data.masterAvgH); deltaS = fabs (data.dupAvgS - data.masterAvgS); deltaV = fabs (data.dupAvgV - data.masterAvgV); // Weigh each delta differently. Note that scores can be negative. hScore = 1.0 - (deltaH * 10.0); sScore = 1.0 - (deltaS * 6.0); vScore = 1.0 - (deltaV * 12.0); // Combine with the actual score. sum = 3 + sqrt (sqrt (data.area)) - 1; data.score = ((data.score * (sqrt(sqrt(data.area)) - 1)) + (hScore + sScore + vScore)) / sum; } /* This recursive function analyzes the image in the HSV color space by breaking the image into subsections and analyzing the subsections. */ void evaluate (Image & dup, Image & master, const int x, const int y, const int width, const int height, Result & total) { Result nw, sw, ne, se; // Compass directions, grand total. Pixel p; // If either the width or height < 1, do nothing. if (width < 1 || height < 1) return; // If the width and height is 1, then initialize the total // to contain the evaluation of the single pixel and stop the recursion. else if (width == 1 && height == 1) { total.area = 1; p = dup.getPixel (x, y); total.dupAvgH = p.getHue (); total.dupAvgS = p.getSaturation (); total.dupAvgV = p.getIntensity (); p = master.getPixel (x, y); total.masterAvgH = p.getHue (); total.masterAvgS = p.getSaturation (); total.masterAvgV = p.getIntensity (); adjustScore (total); return; } // Otherwise, break the region into as many as four sub-regions, evaluate // them, and combine the results. else { evaluate (dup, master, x, y, width/2, height/2, nw); evaluate (dup, master, x + width/2, y, width - width/2, height/2, ne); evaluate (dup, master, x, y + height/2, width/2, height - height/2, sw); evaluate (dup, master, x + width/2, y + height/2, width - width/2, height - height/2, ne); total.area = nw.area + ne.area + sw.area + se.area; total.score = (nw.score * nw.area + ne.score * ne.area + sw.score * sw.area + se.score * se.area) / total.area; total.dupAvgH = (nw.dupAvgH * nw.area + ne.dupAvgH * ne.area + sw.dupAvgH * sw.area + se.dupAvgH * se.area) / total.area; total.dupAvgS = (nw.dupAvgS * nw.area + ne.dupAvgS * ne.area + sw.dupAvgS * sw.area + se.dupAvgS * se.area) / total.area; total.dupAvgV = (nw.dupAvgV * nw.area + ne.dupAvgV * ne.area + sw.dupAvgV * sw.area + se.dupAvgV * se.area) / total.area; total.masterAvgH = (nw.masterAvgH * nw.area + ne.masterAvgH * ne.area + sw.masterAvgH * sw.area + se.masterAvgH * se.area) / total.area; total.masterAvgS = (nw.masterAvgS * nw.area + ne.masterAvgS * ne.area + sw.masterAvgS * sw.area + se.masterAvgS * se.area) / total.area; total.masterAvgV = (nw.masterAvgV * nw.area + ne.masterAvgV * ne.area + sw.masterAvgV * sw.area + se.masterAvgV * se.area) / total.area; adjustScore (total); return; } } int main (int argc, char ** argv) { Image original, clone; double compressionScore; double qualityScore; int originalSize, compressedSize; // Make sure we have the correct number and style of parameters. if ((argc != 2 && argc != 3) || argv[1] == NULL || argv[1][0] == 0) { cout << argv[0] << ':' << endl; cout << " This program evaluates how well the compression class" << endl; cout << "compresses files. It reads a single .BMP file." << endl; cout << "The .BMP file must be true color (24 bit), uncompressed," << endl; cout << "normal bitmap to be read. A score is output. " << endl; cout << endl; cout << "Usage:" << endl; cout << " " << argv[0] << " filename" << endl; cout << endl; cout << "\"filename\" must point to a valid .BMP file, see above." << endl; cout << endl; cout << " You may also use this program to do a quality scoring" << endl; cout << "of two arbitrary images." << endl; cout << endl; cout << "Usage:" << endl; cout << " " << argv[0] << " file1.bmp file2.bmp" << endl; cout << endl; cout << "\"file1\" and \"file2\" must point to valid .BMP files." << endl; cout << endl; return -1; } // Attempt to read in the image. if (! original.readBMP (argv[1], cout)) { cout << "Evaluation failed." << endl; return -1; } // If there was a second image, read it. if (argc == 3) { if (! clone.readBMP (argv[2], cout)) { cout << "Evaluation failed." << endl; return -1; } } // Otherwise, compress and decompress the first image. else { // Figure out the size of the original bitmap. originalSize = original.getHeight() * original.getWidth () * 3 + 54; if (originalSize < 1) { cout << "Zero-sized image." << endl; cout << "Evaluation failed." << endl; return -1; } // Create a string stream for storing the compressed image. char * streamData = new char[originalSize * 3]; ostrstream compressedData(streamData, originalSize * 3); // Compress the image. Compressor highSchoolCompressor; highSchoolCompressor.compress (original, compressedData); // Extract the character stream size and create an input stream // for decompression. compressedSize = compressedData.pcount (); istrstream inputData (streamData, compressedSize); // Decompress the file. clone = highSchoolCompressor.decompress (inputData); // Calculate the compression score. compressionScore = compressedSize / (double) originalSize; } // Calculate the quality score. Result total; evaluate (clone, original, 0, 0, original.getWidth(), original.getHeight(), total); qualityScore = total.score; // Calculate the total score. int totalScore = (int) ((qualityScore - compressionScore) * 100); // Report the scores. if (argc == 2) { cout << "Image: " << argv[1] << endl; cout << "Original size: " << originalSize << endl; cout << "Compressed size: " << compressedSize << endl; cout << endl; cout << "Compression percentage: " << compressionScore*100 << "%" << endl; } cout << "Quality percentage: " << qualityScore * 100 << "%" << endl; cout << endl; if (argc == 2) { cout << "Total score: " << totalScore << endl; cout << endl; } return 0; }