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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: tools/telemetry/telemetry/core/bitmap.py
diff --git a/tools/telemetry/telemetry/core/bitmap.py b/tools/telemetry/telemetry/core/bitmap.py
index 8c52d3b2848649b2a279c24f7c472ce14ea90711..72b1a59ba62abdaaf8ffeb4c8cb16060f6e4e343 100644
--- a/tools/telemetry/telemetry/core/bitmap.py
+++ b/tools/telemetry/telemetry/core/bitmap.py
@@ -1,8 +1,12 @@
# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+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.
import base64
import cStringIO
+import struct
+import subprocess
+import sys
from telemetry.core import util
@@ -43,6 +47,56 @@ WEB_PAGE_TEST_ORANGE = RgbaColor(222, 100, 13)
WHITE = RgbaColor(255, 255, 255)
+class _BitmapTools(object):
+ CROP_PIXELS = 0
+ HISTOGRAM = 1
+ BOUNDING_BOX = 2
+
+ def __init__(self, bitmap):
+ suffix = '.exe' if sys.platform == 'win32' else ''
+ binary = util.FindSupportBinary('bitmaptools' + suffix)
+ assert binary, 'You must build bitmaptools first!'
+
+ self.popen = subprocess.Popen([binary],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ dimensions, pixels = bitmap
+ # dimensions are: bpp, width, height, boxleft, boxtop, boxwidth, boxheight
+ packed_dims = struct.pack('iiiiiii', *dimensions)
+ self.popen.stdin.write(packed_dims)
+ 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
+
+ def _RunCommand(self, *command):
+ packed_command = struct.pack('i' * len(command), *command)
+ self.popen.stdin.write(packed_command)
+ 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
+ length_packed = self.popen.stdout.read(struct.calcsize('i'))
+ if not length_packed:
+ raise Exception(self.popen.stderr.read())
+ length = struct.unpack('i', length_packed)[0]
+ return self.popen.stdout.read(length)
+
+ def CropPixels(self):
+ return self._RunCommand(_BitmapTools.CROP_PIXELS)
+
+ def Histogram(self, ignore_color, tolerance):
+ ignore_color = -1 if ignore_color is None else int(ignore_color)
+ response = self._RunCommand(_BitmapTools.HISTOGRAM, ignore_color, tolerance)
+ out = array.array('i')
+ out.fromstring(response)
+ return out
+
+ def BoundingBox(self, color, tolerance):
+ response = self._RunCommand(_BitmapTools.BOUNDING_BOX, int(color),
+ tolerance)
+ unpacked = struct.unpack('iiiii', response)
+ box, count = unpacked[:4], unpacked[-1]
+ if box[2] < 0 or box[3] < 0:
+ box = None
+ return box, count
+
+
class Bitmap(object):
"""Utilities for parsing and inspecting a bitmap."""
@@ -58,6 +112,7 @@ class Bitmap(object):
self._height = height
self._pixels = pixels
self._metadata = metadata or {}
+ self._crop_box = None
@property
def bpp(self):
@@ -67,16 +122,35 @@ class Bitmap(object):
@property
def width(self):
"""Width of the bitmap."""
+ 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.
+ return self._crop_box[2]
return self._width
@property
def height(self):
"""Height of the bitmap."""
+ if self._crop_box:
+ return self._crop_box[3]
return self._height
@property
+ def _packed(self):
+ # 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,
+ pixels = self._pixels
+ if type(pixels) is not bytearray:
+ pixels = bytearray(pixels)
+ if type(pixels) is not bytes:
+ pixels = bytes(pixels)
+ crop_box = self._crop_box or (0, 0, self._width, self._height)
+ return (self._bpp, self._width, self._height) + crop_box, pixels
+
+ @property
def pixels(self):
"""Flat pixel array of the bitmap."""
+ if self._crop_box:
+ self._pixels = _BitmapTools(self._packed).CropPixels()
+ _, _, self._width, self._height = self._crop_box
+ self._crop_box = None
if type(self._pixels) is not bytearray:
self._pixels = bytearray(self._pixels)
return self._pixels
@@ -90,12 +164,13 @@ class Bitmap(object):
def GetPixelColor(self, x, y):
"""Returns a RgbaColor for the pixel at (x, y)."""
+ pixels = self.pixels
base = self._bpp * (y * self._width + x)
if self._bpp == 4:
- return RgbaColor(self._pixels[base + 0], self._pixels[base + 1],
- self._pixels[base + 2], self._pixels[base + 3])
- return RgbaColor(self._pixels[base + 0], self._pixels[base + 1],
- self._pixels[base + 2])
+ return RgbaColor(pixels[base + 0], pixels[base + 1],
+ pixels[base + 2], pixels[base + 3])
+ return RgbaColor(pixels[base + 0], pixels[base + 1],
+ pixels[base + 2])
def WritePngFile(self, path):
with open(path, "wb") as f:
@@ -179,49 +254,19 @@ class Bitmap(object):
"""Finds the minimum box surrounding all occurences of |color|.
Returns: (top, left, width, height), match_count
Ignores the alpha channel."""
- # TODO(szym): Implement this.
- raise NotImplementedError("GetBoundingBox not yet implemented.")
+ return _BitmapTools(self._packed).BoundingBox(color, tolerance)
def Crop(self, left, top, width, height):
- """Crops the current bitmap down to the specified box.
- TODO(szym): Make this O(1).
- """
+ """Crops the current bitmap down to the specified box."""
+ cur_box = self._crop_box or (0, 0, self._width, self._height)
+ cur_left, cur_top, cur_width, cur_height = cur_box
+
if (left < 0 or top < 0 or
- (left + width) > self.width or
- (top + height) > self.height):
+ (left + width) > cur_width or
+ (top + height) > cur_height):
raise ValueError('Invalid dimensions')
- img_data = [[0 for x in xrange(width * self.bpp)]
- for y in xrange(height)]
-
- # Copy each pixel in the sub-rect.
- # TODO(tonyg): Make this faster by avoiding the copy and artificially
- # restricting the dimensions.
- for y in range(height):
- for x in range(width):
- c = self.GetPixelColor(x + left, y + top)
- offset = x * self.bpp
- img_data[y][offset] = c.r
- img_data[y][offset + 1] = c.g
- img_data[y][offset + 2] = c.b
- if self.bpp == 4:
- img_data[y][offset + 3] = c.a
-
- # This particular method can only save to a file, so the result will be
- # written into an in-memory buffer and read back into a Bitmap
- crop_img = png.from_array(img_data, mode='RGBA' if self.bpp == 4 else 'RGB')
- output = cStringIO.StringIO()
- try:
- crop_img.save(output)
- width, height, pixels, meta = png.Reader(
- bytes=output.getvalue()).read_flat()
- self._width = width
- self._height = height
- self._pixels = pixels
- self._metadata = meta
- finally:
- output.close()
-
+ self._crop_box = cur_left + left, cur_top + top, width, height
return self
def ColorHistogram(self, ignore_color=None, tolerance=0):
@@ -234,5 +279,4 @@ class Bitmap(object):
A list of 3x256 integers formatted as
[r0, r1, ..., g0, g1, ..., b0, b1, ...].
"""
- # TODO(szym): Implement this.
- raise NotImplementedError("ColorHistogram not yet implemented.")
+ return _BitmapTools(self._packed).Histogram(ignore_color, tolerance)

Powered by Google App Engine
This is Rietveld 408576698