237 lines
7.1 KiB
Python
237 lines
7.1 KiB
Python
"""
|
|
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)
|