lmr | 6f669ce | 2009-05-31 19:02:42 +0000 | [diff] [blame] | 1 | import md5, os, struct, time, re |
| 2 | |
| 3 | """ |
| 4 | Utility functions to deal with ppm (qemu screendump format) files. |
| 5 | |
| 6 | @copyright: Red Hat 2008-2009 |
| 7 | """ |
| 8 | |
| 9 | # Some directory/filename utils, for consistency |
| 10 | |
| 11 | def find_id_for_screendump(md5sum, dir): |
| 12 | """ |
| 13 | Search dir for a PPM file whose name ends with md5sum. |
| 14 | |
| 15 | @param md5sum: md5 sum string |
| 16 | @param dir: Directory that holds the PPM files. |
| 17 | @return: The file's basename without any preceding path, e.g. |
| 18 | '20080101_120000_d41d8cd98f00b204e9800998ecf8427e.ppm'. |
| 19 | """ |
| 20 | try: |
| 21 | files = os.listdir(dir) |
| 22 | except OSError: |
| 23 | files = [] |
| 24 | for file in files: |
| 25 | exp = re.compile(r"(.*_)?" + md5sum + r"\.ppm", re.IGNORECASE) |
| 26 | if exp.match(file): |
| 27 | return file |
| 28 | |
| 29 | |
| 30 | def generate_id_for_screendump(md5sum, dir): |
| 31 | """ |
| 32 | Generate a unique filename using the given MD5 sum. |
| 33 | |
| 34 | @return: Only the file basename, without any preceding path. The |
| 35 | filename consists of the current date and time, the MD5 sum and a .ppm |
| 36 | extension, e.g. '20080101_120000_d41d8cd98f00b204e9800998ecf8427e.ppm'. |
| 37 | """ |
| 38 | filename = time.strftime("%Y%m%d_%H%M%S") + "_" + md5sum + ".ppm" |
| 39 | return filename |
| 40 | |
| 41 | |
| 42 | def get_data_dir(steps_filename): |
| 43 | """ |
| 44 | Return the data dir of the given steps filename. |
| 45 | """ |
| 46 | filename = os.path.basename(steps_filename) |
| 47 | return os.path.join(os.path.dirname(steps_filename), "..", "steps_data", |
| 48 | filename + "_data") |
| 49 | |
| 50 | |
| 51 | # Functions for working with PPM files |
| 52 | |
| 53 | def image_read_from_ppm_file(filename): |
| 54 | """ |
| 55 | Read a PPM image. |
| 56 | |
| 57 | @return: A 3 element tuple containing the width, height and data of the |
| 58 | image. |
| 59 | """ |
| 60 | fin = open(filename,"rb") |
| 61 | l1 = fin.readline() |
| 62 | l2 = fin.readline() |
| 63 | l3 = fin.readline() |
| 64 | data = fin.read() |
| 65 | fin.close() |
| 66 | |
| 67 | (w, h) = map(int, l2.split()) |
| 68 | return (w, h, data) |
| 69 | |
| 70 | |
| 71 | def image_write_to_ppm_file(filename, width, height, data): |
| 72 | """ |
| 73 | Write a PPM image with the given width, height and data. |
| 74 | |
| 75 | @param filename: PPM file path |
| 76 | @param width: PPM file width (pixels) |
| 77 | @param height: PPM file height (pixels) |
| 78 | """ |
| 79 | fout = open(filename,"wb") |
| 80 | fout.write("P6\n") |
| 81 | fout.write("%d %d\n" % (width, height)) |
| 82 | fout.write("255\n") |
| 83 | fout.write(data) |
| 84 | fout.close() |
| 85 | |
| 86 | |
| 87 | def image_crop(width, height, data, x1, y1, dx, dy): |
| 88 | """ |
| 89 | Crop an image. |
| 90 | |
| 91 | @param width: Original image width |
| 92 | @param height: Original image height |
| 93 | @param data: Image data |
| 94 | @param x1: Desired x coordinate of the cropped region |
| 95 | @param y1: Desired y coordinate of the cropped region |
| 96 | @param dx: Desired width of the cropped region |
| 97 | @param dy: Desired height of the cropped region |
| 98 | @return: A 3-tuple containing the width, height and data of the |
| 99 | cropped image. |
| 100 | """ |
| 101 | if x1 > width - 1: x1 = width - 1 |
| 102 | if y1 > height - 1: y1 = height - 1 |
| 103 | if dx > width - x1: dx = width - x1 |
| 104 | if dy > height - y1: dy = height - y1 |
| 105 | newdata = "" |
| 106 | index = (x1 + y1*width) * 3 |
| 107 | for i in range(dy): |
| 108 | newdata += data[index:(index+dx*3)] |
| 109 | index += width*3 |
| 110 | return (dx, dy, newdata) |
| 111 | |
| 112 | |
| 113 | def image_md5sum(width, height, data): |
| 114 | """ |
| 115 | Return the md5sum of an image. |
| 116 | |
| 117 | @param width: PPM file width |
| 118 | @param height: PPM file height |
| 119 | @data: PPM file data |
| 120 | """ |
| 121 | header = "P6\n%d %d\n255\n" % (width, height) |
| 122 | md5obj = md5.new(header) |
| 123 | md5obj.update(data) |
| 124 | return md5obj.hexdigest() |
| 125 | |
| 126 | |
| 127 | def get_region_md5sum(width, height, data, x1, y1, dx, dy, |
| 128 | cropped_image_filename=None): |
| 129 | """ |
| 130 | Return the md5sum of a cropped region. |
| 131 | |
| 132 | @param width: Original image width |
| 133 | @param height: Original image height |
| 134 | @param data: Image data |
| 135 | @param x1: Desired x coord of the cropped region |
| 136 | @param y1: Desired y coord of the cropped region |
| 137 | @param dx: Desired width of the cropped region |
| 138 | @param dy: Desired height of the cropped region |
| 139 | @param cropped_image_filename: if not None, write the resulting cropped |
| 140 | image to a file with this name |
| 141 | """ |
| 142 | (cw, ch, cdata) = image_crop(width, height, data, x1, y1, dx, dy) |
| 143 | # Write cropped image for debugging |
| 144 | if cropped_image_filename: |
| 145 | image_write_to_ppm_file(cropped_image_filename, cw, ch, cdata) |
| 146 | return image_md5sum(cw, ch, cdata) |
| 147 | |
| 148 | |
| 149 | def image_verify_ppm_file(filename): |
| 150 | """ |
| 151 | Verify the validity of a PPM file. |
| 152 | |
| 153 | @param filename: Path of the file being verified. |
| 154 | @return: True if filename is a valid PPM image file. This function |
| 155 | reads only the first few bytes of the file so it should be rather fast. |
| 156 | """ |
| 157 | try: |
| 158 | size = os.path.getsize(filename) |
| 159 | fin = open(filename, "rb") |
| 160 | assert(fin.readline().strip() == "P6") |
| 161 | (width, height) = map(int, fin.readline().split()) |
| 162 | assert(width > 0 and height > 0) |
| 163 | assert(fin.readline().strip() == "255") |
| 164 | size_read = fin.tell() |
| 165 | fin.close() |
| 166 | assert(size - size_read == width*height*3) |
| 167 | return True |
| 168 | except: |
| 169 | return False |
| 170 | |
| 171 | |
| 172 | def image_comparison(width, height, data1, data2): |
| 173 | """ |
| 174 | Generate a green-red comparison image from two given images. |
| 175 | |
| 176 | @param width: Width of both images |
| 177 | @param height: Height of both images |
| 178 | @param data1: Data of first image |
| 179 | @param data2: Data of second image |
| 180 | @return: A 3-element tuple containing the width, height and data of the |
| 181 | generated comparison image. |
| 182 | |
| 183 | @note: Input images must be the same size. |
| 184 | """ |
| 185 | newdata = "" |
| 186 | i = 0 |
| 187 | while i < width*height*3: |
| 188 | # Compute monochromatic value of current pixel in data1 |
| 189 | pixel1_str = data1[i:i+3] |
| 190 | temp = struct.unpack("BBB", pixel1_str) |
| 191 | value1 = int((temp[0] + temp[1] + temp[2]) / 3) |
| 192 | # Compute monochromatic value of current pixel in data2 |
| 193 | pixel2_str = data2[i:i+3] |
| 194 | temp = struct.unpack("BBB", pixel2_str) |
| 195 | value2 = int((temp[0] + temp[1] + temp[2]) / 3) |
| 196 | # Compute average of the two values |
| 197 | value = int((value1 + value2) / 2) |
| 198 | # Scale value to the upper half of the range [0, 255] |
| 199 | value = 128 + value / 2 |
| 200 | # Compare pixels |
| 201 | if pixel1_str == pixel2_str: |
| 202 | # Equal -- give the pixel a greenish hue |
| 203 | newpixel = [0, value, 0] |
| 204 | else: |
| 205 | # Not equal -- give the pixel a reddish hue |
| 206 | newpixel = [value, 0, 0] |
| 207 | newdata += struct.pack("BBB", newpixel[0], newpixel[1], newpixel[2]) |
| 208 | i += 3 |
| 209 | return (width, height, newdata) |
| 210 | |
| 211 | |
| 212 | def image_fuzzy_compare(width, height, data1, data2): |
| 213 | """ |
| 214 | Return the degree of equality of two given images. |
| 215 | |
| 216 | @param width: Width of both images |
| 217 | @param height: Height of both images |
| 218 | @param data1: Data of first image |
| 219 | @param data2: Data of second image |
| 220 | @return: Ratio equal_pixel_count / total_pixel_count. |
| 221 | |
| 222 | @note: Input images must be the same size. |
| 223 | """ |
| 224 | equal = 0.0 |
| 225 | different = 0.0 |
| 226 | i = 0 |
| 227 | while i < width*height*3: |
| 228 | pixel1_str = data1[i:i+3] |
| 229 | pixel2_str = data2[i:i+3] |
| 230 | # Compare pixels |
| 231 | if pixel1_str == pixel2_str: |
| 232 | equal += 1.0 |
| 233 | else: |
| 234 | different += 1.0 |
| 235 | i += 3 |
| 236 | return equal / (equal + different) |