OLD | NEW |
| (Empty) |
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 # Use of this source code is governed by a BSD-style license that can be | |
3 # found in the LICENSE file. | |
4 import base64 | |
5 import cStringIO | |
6 | |
7 from telemetry.core import util | |
8 | |
9 util.AddDirToPythonPath(util.GetTelemetryDir(), 'third_party', 'png') | |
10 import png # pylint: disable=F0401 | |
11 | |
12 | |
13 class PngColor(object): | |
14 """Encapsulates an RGB color retreived from a PngBitmap""" | |
15 | |
16 def __init__(self, r, g, b, a=255): | |
17 self.r = r | |
18 self.g = g | |
19 self.b = b | |
20 self.a = a | |
21 | |
22 def IsEqual(self, expected_color, tolerance=0): | |
23 """Verifies that the color is within a given tolerance of | |
24 the expected color""" | |
25 r_diff = abs(self.r - expected_color.r) | |
26 g_diff = abs(self.g - expected_color.g) | |
27 b_diff = abs(self.b - expected_color.b) | |
28 a_diff = abs(self.a - expected_color.a) | |
29 return (r_diff <= tolerance and g_diff <= tolerance | |
30 and b_diff <= tolerance and a_diff <= tolerance) | |
31 | |
32 def AssertIsRGB(self, r, g, b, tolerance=0): | |
33 assert self.IsEqual(PngColor(r, g, b), tolerance) | |
34 | |
35 def AssertIsRGBA(self, r, g, b, a, tolerance=0): | |
36 assert self.IsEqual(PngColor(r, g, b, a), tolerance) | |
37 | |
38 | |
39 class PngBitmap(object): | |
40 """Utilities for parsing and inspecting a PNG""" | |
41 | |
42 def __init__(self, png_data): | |
43 self._png_data = png_data | |
44 self._png = png.Reader(bytes=self._png_data) | |
45 rgba8_data = self._png.asRGBA8() | |
46 self._width = rgba8_data[0] | |
47 self._height = rgba8_data[1] | |
48 self._pixels = list(rgba8_data[2]) | |
49 self._metadata = rgba8_data[3] | |
50 | |
51 @property | |
52 def width(self): | |
53 """Width of the snapshot""" | |
54 return self._width | |
55 | |
56 @property | |
57 def height(self): | |
58 """Height of the snapshot""" | |
59 return self._height | |
60 | |
61 def GetPixelColor(self, x, y): | |
62 """Returns a PngColor for the pixel at (x, y)""" | |
63 row = self._pixels[y] | |
64 offset = x * 4 | |
65 return PngColor(row[offset], row[offset+1], row[offset+2], row[offset+3]) | |
66 | |
67 def WriteFile(self, path): | |
68 with open(path, "wb") as f: | |
69 f.write(self._png_data) | |
70 | |
71 @staticmethod | |
72 def FromFile(path): | |
73 with open(path, "rb") as f: | |
74 return PngBitmap(f.read()) | |
75 | |
76 @staticmethod | |
77 def FromBase64(base64_png): | |
78 return PngBitmap(base64.b64decode(base64_png)) | |
79 | |
80 def IsEqual(self, expected_png, tolerance=0): | |
81 """Verifies that two PngBitmaps are identical within a given tolerance""" | |
82 | |
83 # Dimensions must be equal | |
84 if self.width != expected_png.width or self.height != expected_png.height: | |
85 return False | |
86 | |
87 # Loop over each pixel and test for equality | |
88 for y in range(self.height): | |
89 for x in range(self.width): | |
90 c0 = self.GetPixelColor(x, y) | |
91 c1 = expected_png.GetPixelColor(x, y) | |
92 if not c0.IsEqual(c1, tolerance): | |
93 return False | |
94 | |
95 return True | |
96 | |
97 def Diff(self, other_png): | |
98 """Returns a new PngBitmap that represents the difference between this image | |
99 and another PngBitmap""" | |
100 | |
101 # Output dimensions will be the maximum of the two input dimensions | |
102 out_width = max(self.width, other_png.width) | |
103 out_height = max(self.height, other_png.height) | |
104 | |
105 diff = [[0 for x in xrange(out_width * 3)] for x in xrange(out_height)] | |
106 | |
107 # Loop over each pixel and test for equality | |
108 for y in range(out_height): | |
109 for x in range(out_width): | |
110 if x < self.width and y < self.height: | |
111 c0 = self.GetPixelColor(x, y) | |
112 else: | |
113 c0 = PngColor(0, 0, 0, 0) | |
114 | |
115 if x < other_png.width and y < other_png.height: | |
116 c1 = other_png.GetPixelColor(x, y) | |
117 else: | |
118 c1 = PngColor(0, 0, 0, 0) | |
119 | |
120 offset = x * 3 | |
121 diff[y][offset] = abs(c0.r - c1.r) | |
122 diff[y][offset+1] = abs(c0.g - c1.g) | |
123 diff[y][offset+2] = abs(c0.b - c1.b) | |
124 | |
125 # This particular method can only save to a file, so the result will be | |
126 # written into an in-memory buffer and read back into a PngBitmap | |
127 diff_img = png.from_array(diff, mode='RGB') | |
128 output = cStringIO.StringIO() | |
129 try: | |
130 diff_img.save(output) | |
131 diff_png = PngBitmap(output.getvalue()) | |
132 finally: | |
133 output.close() | |
134 | |
135 return diff_png | |
136 | |
OLD | NEW |