blob: f0435cee53604cfbf454aa06eda47fe98053c1c6 [file] [log] [blame]
lmr6f669ce2009-05-31 19:02:42 +00001import md5, os, struct, time, re
2
3"""
4Utility 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
11def 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
30def 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
42def 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
53def 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
71def 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
87def 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
113def 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
127def 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
149def 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
172def 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
212def 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)