| """ |
| Utility functions to deal with ppm (qemu screendump format) files. |
| |
| @copyright: Red Hat 2008-2009 |
| """ |
| |
| import os, struct, time, re |
| from autotest_lib.client.bin import utils |
| |
| # Some directory/filename utils, for consistency |
| |
| def find_id_for_screendump(md5sum, dir): |
| """ |
| Search dir for a PPM file whose name ends with md5sum. |
| |
| @param md5sum: md5 sum string |
| @param dir: Directory that holds the PPM files. |
| @return: The file's basename without any preceding path, e.g. |
| '20080101_120000_d41d8cd98f00b204e9800998ecf8427e.ppm'. |
| """ |
| try: |
| files = os.listdir(dir) |
| except OSError: |
| files = [] |
| for file in files: |
| exp = re.compile(r"(.*_)?" + md5sum + r"\.ppm", re.IGNORECASE) |
| if exp.match(file): |
| return file |
| |
| |
| def generate_id_for_screendump(md5sum, dir): |
| """ |
| Generate a unique filename using the given MD5 sum. |
| |
| @return: Only the file basename, without any preceding path. The |
| filename consists of the current date and time, the MD5 sum and a .ppm |
| extension, e.g. '20080101_120000_d41d8cd98f00b204e9800998ecf8427e.ppm'. |
| """ |
| filename = time.strftime("%Y%m%d_%H%M%S") + "_" + md5sum + ".ppm" |
| return filename |
| |
| |
| def get_data_dir(steps_filename): |
| """ |
| Return the data dir of the given steps filename. |
| """ |
| filename = os.path.basename(steps_filename) |
| return os.path.join(os.path.dirname(steps_filename), "..", "steps_data", |
| filename + "_data") |
| |
| |
| # Functions for working with PPM files |
| |
| def image_read_from_ppm_file(filename): |
| """ |
| Read a PPM image. |
| |
| @return: A 3 element tuple containing the width, height and data of the |
| image. |
| """ |
| fin = open(filename,"rb") |
| l1 = fin.readline() |
| l2 = fin.readline() |
| l3 = fin.readline() |
| data = fin.read() |
| fin.close() |
| |
| (w, h) = map(int, l2.split()) |
| return (w, h, data) |
| |
| |
| def image_write_to_ppm_file(filename, width, height, data): |
| """ |
| Write a PPM image with the given width, height and data. |
| |
| @param filename: PPM file path |
| @param width: PPM file width (pixels) |
| @param height: PPM file height (pixels) |
| """ |
| fout = open(filename,"wb") |
| fout.write("P6\n") |
| fout.write("%d %d\n" % (width, height)) |
| fout.write("255\n") |
| fout.write(data) |
| fout.close() |
| |
| |
| def image_crop(width, height, data, x1, y1, dx, dy): |
| """ |
| Crop an image. |
| |
| @param width: Original image width |
| @param height: Original image height |
| @param data: Image data |
| @param x1: Desired x coordinate of the cropped region |
| @param y1: Desired y coordinate of the cropped region |
| @param dx: Desired width of the cropped region |
| @param dy: Desired height of the cropped region |
| @return: A 3-tuple containing the width, height and data of the |
| cropped image. |
| """ |
| if x1 > width - 1: x1 = width - 1 |
| if y1 > height - 1: y1 = height - 1 |
| if dx > width - x1: dx = width - x1 |
| if dy > height - y1: dy = height - y1 |
| newdata = "" |
| index = (x1 + y1*width) * 3 |
| for i in range(dy): |
| newdata += data[index:(index+dx*3)] |
| index += width*3 |
| return (dx, dy, newdata) |
| |
| |
| def image_md5sum(width, height, data): |
| """ |
| Return the md5sum of an image. |
| |
| @param width: PPM file width |
| @param height: PPM file height |
| @data: PPM file data |
| """ |
| header = "P6\n%d %d\n255\n" % (width, height) |
| hash = utils.hash('md5', header) |
| hash.update(data) |
| return hash.hexdigest() |
| |
| |
| def get_region_md5sum(width, height, data, x1, y1, dx, dy, |
| cropped_image_filename=None): |
| """ |
| Return the md5sum of a cropped region. |
| |
| @param width: Original image width |
| @param height: Original image height |
| @param data: Image data |
| @param x1: Desired x coord of the cropped region |
| @param y1: Desired y coord of the cropped region |
| @param dx: Desired width of the cropped region |
| @param dy: Desired height of the cropped region |
| @param cropped_image_filename: if not None, write the resulting cropped |
| image to a file with this name |
| """ |
| (cw, ch, cdata) = image_crop(width, height, data, x1, y1, dx, dy) |
| # Write cropped image for debugging |
| if cropped_image_filename: |
| image_write_to_ppm_file(cropped_image_filename, cw, ch, cdata) |
| return image_md5sum(cw, ch, cdata) |
| |
| |
| def image_verify_ppm_file(filename): |
| """ |
| Verify the validity of a PPM file. |
| |
| @param filename: Path of the file being verified. |
| @return: True if filename is a valid PPM image file. This function |
| reads only the first few bytes of the file so it should be rather fast. |
| """ |
| try: |
| size = os.path.getsize(filename) |
| fin = open(filename, "rb") |
| assert(fin.readline().strip() == "P6") |
| (width, height) = map(int, fin.readline().split()) |
| assert(width > 0 and height > 0) |
| assert(fin.readline().strip() == "255") |
| size_read = fin.tell() |
| fin.close() |
| assert(size - size_read == width*height*3) |
| return True |
| except: |
| return False |
| |
| |
| def image_comparison(width, height, data1, data2): |
| """ |
| Generate a green-red comparison image from two given images. |
| |
| @param width: Width of both images |
| @param height: Height of both images |
| @param data1: Data of first image |
| @param data2: Data of second image |
| @return: A 3-element tuple containing the width, height and data of the |
| generated comparison image. |
| |
| @note: Input images must be the same size. |
| """ |
| newdata = "" |
| i = 0 |
| while i < width*height*3: |
| # Compute monochromatic value of current pixel in data1 |
| pixel1_str = data1[i:i+3] |
| temp = struct.unpack("BBB", pixel1_str) |
| value1 = int((temp[0] + temp[1] + temp[2]) / 3) |
| # Compute monochromatic value of current pixel in data2 |
| pixel2_str = data2[i:i+3] |
| temp = struct.unpack("BBB", pixel2_str) |
| value2 = int((temp[0] + temp[1] + temp[2]) / 3) |
| # Compute average of the two values |
| value = int((value1 + value2) / 2) |
| # Scale value to the upper half of the range [0, 255] |
| value = 128 + value / 2 |
| # Compare pixels |
| if pixel1_str == pixel2_str: |
| # Equal -- give the pixel a greenish hue |
| newpixel = [0, value, 0] |
| else: |
| # Not equal -- give the pixel a reddish hue |
| newpixel = [value, 0, 0] |
| newdata += struct.pack("BBB", newpixel[0], newpixel[1], newpixel[2]) |
| i += 3 |
| return (width, height, newdata) |
| |
| |
| def image_fuzzy_compare(width, height, data1, data2): |
| """ |
| Return the degree of equality of two given images. |
| |
| @param width: Width of both images |
| @param height: Height of both images |
| @param data1: Data of first image |
| @param data2: Data of second image |
| @return: Ratio equal_pixel_count / total_pixel_count. |
| |
| @note: Input images must be the same size. |
| """ |
| equal = 0.0 |
| different = 0.0 |
| i = 0 |
| while i < width*height*3: |
| pixel1_str = data1[i:i+3] |
| pixel2_str = data2[i:i+3] |
| # Compare pixels |
| if pixel1_str == pixel2_str: |
| equal += 1.0 |
| else: |
| different += 1.0 |
| i += 3 |
| return equal / (equal + different) |