Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(152)

Side by Side Diff: tools/telemetry/telemetry/core/bitmap.py

Issue 136793022: [telemetry] bitmaptools as a standalone executable (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: look for bitmaptools.exe on win32 Created 6 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 # Copyright 2013 The Chromium Authors. All rights reserved. 1 # Copyright 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 import array
M-A Ruel 2014/01/17 14:01:37 insert an empty line What's the purpose of this f
szym 2014/01/17 20:38:20 Done.
4 import base64 5 import base64
5 import cStringIO 6 import cStringIO
7 import struct
8 import subprocess
9 import sys
6 10
7 from telemetry.core import util 11 from telemetry.core import util
8 12
9 util.AddDirToPythonPath(util.GetTelemetryDir(), 'third_party', 'png') 13 util.AddDirToPythonPath(util.GetTelemetryDir(), 'third_party', 'png')
10 import png # pylint: disable=F0401 14 import png # pylint: disable=F0401
11 15
12 16
13 class RgbaColor(object): 17 class RgbaColor(object):
14 """Encapsulates an RGBA color retreived from a Bitmap""" 18 """Encapsulates an RGBA color retreived from a Bitmap"""
15 19
(...skipping 20 matching lines...) Expand all
36 assert self.IsEqual(RgbaColor(r, g, b), tolerance) 40 assert self.IsEqual(RgbaColor(r, g, b), tolerance)
37 41
38 def AssertIsRGBA(self, r, g, b, a, tolerance=0): 42 def AssertIsRGBA(self, r, g, b, a, tolerance=0):
39 assert self.IsEqual(RgbaColor(r, g, b, a), tolerance) 43 assert self.IsEqual(RgbaColor(r, g, b, a), tolerance)
40 44
41 45
42 WEB_PAGE_TEST_ORANGE = RgbaColor(222, 100, 13) 46 WEB_PAGE_TEST_ORANGE = RgbaColor(222, 100, 13)
43 WHITE = RgbaColor(255, 255, 255) 47 WHITE = RgbaColor(255, 255, 255)
44 48
45 49
50 class _BitmapTools(object):
51 CROP_PIXELS = 0
52 HISTOGRAM = 1
53 BOUNDING_BOX = 2
54
55 def __init__(self, bitmap):
56 suffix = '.exe' if sys.platform == 'win32' else ''
57 binary = util.FindSupportBinary('bitmaptools' + suffix)
58 assert binary, 'You must build bitmaptools first!'
59
60 self.popen = subprocess.Popen([binary],
61 stdin=subprocess.PIPE,
62 stdout=subprocess.PIPE,
63 stderr=subprocess.PIPE)
64 dimensions, pixels = bitmap
65 # dimensions are: bpp, width, height, boxleft, boxtop, boxwidth, boxheight
66 packed_dims = struct.pack('iiiiiii', *dimensions)
67 self.popen.stdin.write(packed_dims)
68 self.popen.stdin.write(pixels)
bulach 2014/01/17 10:43:44 nit: perhaps a self.popen.flush() here?
M-A Ruel 2014/01/17 14:01:37 well, keep in mind this code could dead lock.
szym 2014/01/17 20:38:20 We don't read from popen.stdout until _RunCommand
69
70 def _RunCommand(self, *command):
71 packed_command = struct.pack('i' * len(command), *command)
72 self.popen.stdin.write(packed_command)
73 self.popen.stdin.close()
M-A Ruel 2014/01/17 14:01:37 So the object can only have one call done on it an
szym 2014/01/17 20:38:20 I want to reuse _RunCommand and __init__. I tried
74 length_packed = self.popen.stdout.read(struct.calcsize('i'))
75 if not length_packed:
76 raise Exception(self.popen.stderr.read())
77 length = struct.unpack('i', length_packed)[0]
78 return self.popen.stdout.read(length)
79
80 def CropPixels(self):
81 return self._RunCommand(_BitmapTools.CROP_PIXELS)
82
83 def Histogram(self, ignore_color, tolerance):
84 ignore_color = -1 if ignore_color is None else int(ignore_color)
85 response = self._RunCommand(_BitmapTools.HISTOGRAM, ignore_color, tolerance)
86 out = array.array('i')
87 out.fromstring(response)
88 return out
89
90 def BoundingBox(self, color, tolerance):
91 response = self._RunCommand(_BitmapTools.BOUNDING_BOX, int(color),
92 tolerance)
93 unpacked = struct.unpack('iiiii', response)
94 box, count = unpacked[:4], unpacked[-1]
95 if box[2] < 0 or box[3] < 0:
96 box = None
97 return box, count
98
99
46 class Bitmap(object): 100 class Bitmap(object):
47 """Utilities for parsing and inspecting a bitmap.""" 101 """Utilities for parsing and inspecting a bitmap."""
48 102
49 def __init__(self, bpp, width, height, pixels, metadata=None): 103 def __init__(self, bpp, width, height, pixels, metadata=None):
50 assert bpp in [3, 4], 'Invalid bytes per pixel' 104 assert bpp in [3, 4], 'Invalid bytes per pixel'
51 assert width > 0, 'Invalid width' 105 assert width > 0, 'Invalid width'
52 assert height > 0, 'Invalid height' 106 assert height > 0, 'Invalid height'
53 assert pixels, 'Must specify pixels' 107 assert pixels, 'Must specify pixels'
54 assert bpp * width * height == len(pixels), 'Dimensions and pixels mismatch' 108 assert bpp * width * height == len(pixels), 'Dimensions and pixels mismatch'
55 109
56 self._bpp = bpp 110 self._bpp = bpp
57 self._width = width 111 self._width = width
58 self._height = height 112 self._height = height
59 self._pixels = pixels 113 self._pixels = pixels
60 self._metadata = metadata or {} 114 self._metadata = metadata or {}
115 self._crop_box = None
61 116
62 @property 117 @property
63 def bpp(self): 118 def bpp(self):
64 """Bytes per pixel.""" 119 """Bytes per pixel."""
65 return self._bpp 120 return self._bpp
66 121
67 @property 122 @property
68 def width(self): 123 def width(self):
69 """Width of the bitmap.""" 124 """Width of the bitmap."""
125 if self._crop_box:
M-A Ruel 2014/01/17 14:01:37 optionally, if you want to save space, you do this
szym 2014/01/17 20:38:20 Done.
126 return self._crop_box[2]
70 return self._width 127 return self._width
71 128
72 @property 129 @property
73 def height(self): 130 def height(self):
74 """Height of the bitmap.""" 131 """Height of the bitmap."""
132 if self._crop_box:
133 return self._crop_box[3]
75 return self._height 134 return self._height
76 135
77 @property 136 @property
137 def _packed(self):
138 # If we got a list of ints, we need to convert it into a byte buffer.
M-A Ruel 2014/01/17 14:01:37 As a docstring?
szym 2014/01/17 20:38:20 This part describes just the |pixels| conversion,
139 pixels = self._pixels
140 if type(pixels) is not bytearray:
141 pixels = bytearray(pixels)
142 if type(pixels) is not bytes:
143 pixels = bytes(pixels)
144 crop_box = self._crop_box or (0, 0, self._width, self._height)
145 return (self._bpp, self._width, self._height) + crop_box, pixels
146
147 @property
78 def pixels(self): 148 def pixels(self):
79 """Flat pixel array of the bitmap.""" 149 """Flat pixel array of the bitmap."""
150 if self._crop_box:
151 self._pixels = _BitmapTools(self._packed).CropPixels()
152 _, _, self._width, self._height = self._crop_box
153 self._crop_box = None
80 if type(self._pixels) is not bytearray: 154 if type(self._pixels) is not bytearray:
81 self._pixels = bytearray(self._pixels) 155 self._pixels = bytearray(self._pixels)
82 return self._pixels 156 return self._pixels
83 157
84 @property 158 @property
85 def metadata(self): 159 def metadata(self):
86 self._metadata['size'] = (self.width, self.height) 160 self._metadata['size'] = (self.width, self.height)
87 self._metadata['alpha'] = self.bpp == 4 161 self._metadata['alpha'] = self.bpp == 4
88 self._metadata['bitdepth'] = 8 162 self._metadata['bitdepth'] = 8
89 return self._metadata 163 return self._metadata
90 164
91 def GetPixelColor(self, x, y): 165 def GetPixelColor(self, x, y):
92 """Returns a RgbaColor for the pixel at (x, y).""" 166 """Returns a RgbaColor for the pixel at (x, y)."""
167 pixels = self.pixels
93 base = self._bpp * (y * self._width + x) 168 base = self._bpp * (y * self._width + x)
94 if self._bpp == 4: 169 if self._bpp == 4:
95 return RgbaColor(self._pixels[base + 0], self._pixels[base + 1], 170 return RgbaColor(pixels[base + 0], pixels[base + 1],
96 self._pixels[base + 2], self._pixels[base + 3]) 171 pixels[base + 2], pixels[base + 3])
97 return RgbaColor(self._pixels[base + 0], self._pixels[base + 1], 172 return RgbaColor(pixels[base + 0], pixels[base + 1],
98 self._pixels[base + 2]) 173 pixels[base + 2])
99 174
100 def WritePngFile(self, path): 175 def WritePngFile(self, path):
101 with open(path, "wb") as f: 176 with open(path, "wb") as f:
102 png.Writer(**self.metadata).write_array(f, self.pixels) 177 png.Writer(**self.metadata).write_array(f, self.pixels)
103 178
104 @staticmethod 179 @staticmethod
105 def FromPng(png_data): 180 def FromPng(png_data):
106 width, height, pixels, meta = png.Reader(bytes=png_data).read_flat() 181 width, height, pixels, meta = png.Reader(bytes=png_data).read_flat()
107 return Bitmap(4 if meta['alpha'] else 3, width, height, pixels, meta) 182 return Bitmap(4 if meta['alpha'] else 3, width, height, pixels, meta)
108 183
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after
172 diff = Bitmap.FromPng(output.getvalue()) 247 diff = Bitmap.FromPng(output.getvalue())
173 finally: 248 finally:
174 output.close() 249 output.close()
175 250
176 return diff 251 return diff
177 252
178 def GetBoundingBox(self, color, tolerance=0): 253 def GetBoundingBox(self, color, tolerance=0):
179 """Finds the minimum box surrounding all occurences of |color|. 254 """Finds the minimum box surrounding all occurences of |color|.
180 Returns: (top, left, width, height), match_count 255 Returns: (top, left, width, height), match_count
181 Ignores the alpha channel.""" 256 Ignores the alpha channel."""
182 # TODO(szym): Implement this. 257 return _BitmapTools(self._packed).BoundingBox(color, tolerance)
183 raise NotImplementedError("GetBoundingBox not yet implemented.")
184 258
185 def Crop(self, left, top, width, height): 259 def Crop(self, left, top, width, height):
186 """Crops the current bitmap down to the specified box. 260 """Crops the current bitmap down to the specified box."""
187 TODO(szym): Make this O(1). 261 cur_box = self._crop_box or (0, 0, self._width, self._height)
188 """ 262 cur_left, cur_top, cur_width, cur_height = cur_box
263
189 if (left < 0 or top < 0 or 264 if (left < 0 or top < 0 or
190 (left + width) > self.width or 265 (left + width) > cur_width or
191 (top + height) > self.height): 266 (top + height) > cur_height):
192 raise ValueError('Invalid dimensions') 267 raise ValueError('Invalid dimensions')
193 268
194 img_data = [[0 for x in xrange(width * self.bpp)] 269 self._crop_box = cur_left + left, cur_top + top, width, height
195 for y in xrange(height)]
196
197 # Copy each pixel in the sub-rect.
198 # TODO(tonyg): Make this faster by avoiding the copy and artificially
199 # restricting the dimensions.
200 for y in range(height):
201 for x in range(width):
202 c = self.GetPixelColor(x + left, y + top)
203 offset = x * self.bpp
204 img_data[y][offset] = c.r
205 img_data[y][offset + 1] = c.g
206 img_data[y][offset + 2] = c.b
207 if self.bpp == 4:
208 img_data[y][offset + 3] = c.a
209
210 # This particular method can only save to a file, so the result will be
211 # written into an in-memory buffer and read back into a Bitmap
212 crop_img = png.from_array(img_data, mode='RGBA' if self.bpp == 4 else 'RGB')
213 output = cStringIO.StringIO()
214 try:
215 crop_img.save(output)
216 width, height, pixels, meta = png.Reader(
217 bytes=output.getvalue()).read_flat()
218 self._width = width
219 self._height = height
220 self._pixels = pixels
221 self._metadata = meta
222 finally:
223 output.close()
224
225 return self 270 return self
226 271
227 def ColorHistogram(self, ignore_color=None, tolerance=0): 272 def ColorHistogram(self, ignore_color=None, tolerance=0):
228 """Computes a histogram of the pixel colors in this Bitmap. 273 """Computes a histogram of the pixel colors in this Bitmap.
229 Args: 274 Args:
230 ignore_color: An RgbaColor to exclude from the bucket counts. 275 ignore_color: An RgbaColor to exclude from the bucket counts.
231 tolerance: A tolerance for the ignore_color. 276 tolerance: A tolerance for the ignore_color.
232 277
233 Returns: 278 Returns:
234 A list of 3x256 integers formatted as 279 A list of 3x256 integers formatted as
235 [r0, r1, ..., g0, g1, ..., b0, b1, ...]. 280 [r0, r1, ..., g0, g1, ..., b0, b1, ...].
236 """ 281 """
237 # TODO(szym): Implement this. 282 return _BitmapTools(self._packed).Histogram(ignore_color, tolerance)
238 raise NotImplementedError("ColorHistogram not yet implemented.")
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698