133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck# Copyright 2013 The Chromium Authors. All rights reserved.
233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck# Use of this source code is governed by a BSD-style license that can be
333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck# found in the LICENSE file.
433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck"""
633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John ReckBitmap is a basic wrapper for image pixels. It includes some basic processing
733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Recktools: crop, find bounding box of a color and compute histogram of color values.
833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck"""
933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
1033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckimport array
1133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckimport cStringIO
1233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckimport struct
1333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckimport subprocess
1433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckimport warnings
1533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
1633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckfrom telemetry.internal.util import binary_manager
1733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckfrom telemetry.core import platform
1833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckfrom telemetry.util import color_histogram
1933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckfrom telemetry.util import rgba_color
2033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
2133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckimport png
2233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
2333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
2433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckclass _BitmapTools(object):
2533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  """Wraps a child process of bitmaptools and allows for one command."""
2633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  CROP_PIXELS = 0
2733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  HISTOGRAM = 1
2833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  BOUNDING_BOX = 2
2933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
3033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def __init__(self, dimensions, pixels):
3133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    binary = binary_manager.FetchPath(
3233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        'bitmaptools',
3333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        platform.GetHostPlatform().GetArchName(),
3433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        platform.GetHostPlatform().GetOSName())
3533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    assert binary, 'You must build bitmaptools first!'
3633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
3733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._popen = subprocess.Popen([binary],
3833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck                                   stdin=subprocess.PIPE,
3933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck                                   stdout=subprocess.PIPE,
4033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck                                   stderr=subprocess.PIPE)
4133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
4233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    # dimensions are: bpp, width, height, boxleft, boxtop, boxwidth, boxheight
4333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    packed_dims = struct.pack('iiiiiii', *dimensions)
4433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._popen.stdin.write(packed_dims)
4533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    # If we got a list of ints, we need to convert it into a byte buffer.
4633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if type(pixels) is not bytearray:
4733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      pixels = bytearray(pixels)
4833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._popen.stdin.write(pixels)
4933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
5033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def _RunCommand(self, *command):
5133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    assert not self._popen.stdin.closed, (
5233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      'Exactly one command allowed per instance of tools.')
5333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    packed_command = struct.pack('i' * len(command), *command)
5433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._popen.stdin.write(packed_command)
5533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._popen.stdin.close()
5633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    length_packed = self._popen.stdout.read(struct.calcsize('i'))
5733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if not length_packed:
5833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      raise Exception(self._popen.stderr.read())
5933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    length = struct.unpack('i', length_packed)[0]
6033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return self._popen.stdout.read(length)
6133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
6233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def CropPixels(self):
6333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return self._RunCommand(_BitmapTools.CROP_PIXELS)
6433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
6533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def Histogram(self, ignore_color, tolerance):
6633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    ignore_color_int = -1 if ignore_color is None else int(ignore_color)
6733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    response = self._RunCommand(_BitmapTools.HISTOGRAM,
6833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck                                ignore_color_int, tolerance)
6933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    out = array.array('i')
7033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    out.fromstring(response)
7133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    assert len(out) == 768, (
7233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        'The ColorHistogram has the wrong number of buckets: %s' % len(out))
7333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return color_histogram.ColorHistogram(out[:256], out[256:512], out[512:],
7433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck                                    ignore_color)
7533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
7633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def BoundingBox(self, color, tolerance):
7733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    response = self._RunCommand(_BitmapTools.BOUNDING_BOX, int(color),
7833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck                                tolerance)
7933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    unpacked = struct.unpack('iiiii', response)
8033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    box, count = unpacked[:4], unpacked[-1]
8133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if box[2] < 0 or box[3] < 0:
8233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      box = None
8333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return box, count
8433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
8533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
8633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckclass Bitmap(object):
8733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  """Utilities for parsing and inspecting a bitmap."""
8833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
8933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def __init__(self, bpp, width, height, pixels, metadata=None):
9033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    assert bpp in [3, 4], 'Invalid bytes per pixel'
9133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    assert width > 0, 'Invalid width'
9233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    assert height > 0, 'Invalid height'
9333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    assert pixels, 'Must specify pixels'
9433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    assert bpp * width * height == len(pixels), 'Dimensions and pixels mismatch'
9533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
9633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._bpp = bpp
9733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._width = width
9833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._height = height
9933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._pixels = pixels
10033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._metadata = metadata or {}
10133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._crop_box = None
10233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
10333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  @property
10433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def bpp(self):
10533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return self._bpp
10633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
10733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  @property
10833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def width(self):
10933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return self._crop_box[2] if self._crop_box else self._width
11033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
11133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  @property
11233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def height(self):
11333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return self._crop_box[3] if self._crop_box else self._height
11433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
11533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def _PrepareTools(self):
11633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    """Prepares an instance of _BitmapTools which allows exactly one command.
11733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    """
11833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    crop_box = self._crop_box or (0, 0, self._width, self._height)
11933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return _BitmapTools((self._bpp, self._width, self._height) + crop_box,
12033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck                        self._pixels)
12133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
12233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  @property
12333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def pixels(self):
12433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if self._crop_box:
12533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      self._pixels = self._PrepareTools().CropPixels()
12633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      # pylint: disable=unpacking-non-sequence
12733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      _, _, self._width, self._height = self._crop_box
12833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      self._crop_box = None
12933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if type(self._pixels) is not bytearray:
13033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      self._pixels = bytearray(self._pixels)
13133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return self._pixels
13233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
13333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  @property
13433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def metadata(self):
13533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._metadata['size'] = (self.width, self.height)
13633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._metadata['alpha'] = self.bpp == 4
13733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._metadata['bitdepth'] = 8
13833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return self._metadata
13933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
14033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def GetPixelColor(self, x, y):
14133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    pixels = self.pixels
14233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    base = self._bpp * (y * self._width + x)
14333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if self._bpp == 4:
14433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      return rgba_color.RgbaColor(pixels[base + 0], pixels[base + 1],
14533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck                                  pixels[base + 2], pixels[base + 3])
14633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return rgba_color.RgbaColor(pixels[base + 0], pixels[base + 1],
14733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck                                pixels[base + 2])
14833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
14933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  @staticmethod
15033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def FromPng(png_data):
15133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    warnings.warn(
15233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        'Using pure python png decoder, which could be very slow. To speed up, '
15333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        'consider installing numpy & cv2 (OpenCV).')
15433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    width, height, pixels, meta = png.Reader(bytes=png_data).read_flat()
15533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return Bitmap(4 if meta['alpha'] else 3, width, height, pixels, meta)
15633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
15733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  @staticmethod
15833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def FromPngFile(path):
15933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    with open(path, "rb") as f:
16033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      return Bitmap.FromPng(f.read())
16133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
16233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def WritePngFile(self, path):
16333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    with open(path, "wb") as f:
16433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      png.Writer(**self.metadata).write_array(f, self.pixels)
16533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
16633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def IsEqual(self, other, tolerance=0):
16733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    # Dimensions must be equal
16833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if self.width != other.width or self.height != other.height:
16933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      return False
17033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
17133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    # Loop over each pixel and test for equality
17233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if tolerance or self.bpp != other.bpp:
17333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      for y in range(self.height):
17433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        for x in range(self.width):
17533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck          c0 = self.GetPixelColor(x, y)
17633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck          c1 = other.GetPixelColor(x, y)
17733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck          if not c0.IsEqual(c1, tolerance):
17833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck            return False
17933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    else:
18033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      return self.pixels == other.pixels
18133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
18233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return True
18333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
18433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def Diff(self, other):
18533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    # Output dimensions will be the maximum of the two input dimensions
18633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    out_width = max(self.width, other.width)
18733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    out_height = max(self.height, other.height)
18833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
18933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    diff = [[0 for x in xrange(out_width * 3)] for x in xrange(out_height)]
19033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
19133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    # Loop over each pixel and write out the difference
19233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    for y in range(out_height):
19333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      for x in range(out_width):
19433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        if x < self.width and y < self.height:
19533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck          c0 = self.GetPixelColor(x, y)
19633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        else:
19733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck          c0 = rgba_color.RgbaColor(0, 0, 0, 0)
19833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
19933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        if x < other.width and y < other.height:
20033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck          c1 = other.GetPixelColor(x, y)
20133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        else:
20233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck          c1 = rgba_color.RgbaColor(0, 0, 0, 0)
20333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
20433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        offset = x * 3
20533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        diff[y][offset] = abs(c0.r - c1.r)
20633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        diff[y][offset+1] = abs(c0.g - c1.g)
20733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        diff[y][offset+2] = abs(c0.b - c1.b)
20833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
20933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    # This particular method can only save to a file, so the result will be
21033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    # written into an in-memory buffer and read back into a Bitmap
21133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    warnings.warn(
21233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        'Using pure python png decoder, which could be very slow. To speed up, '
21333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        'consider installing numpy & cv2 (OpenCV).')
21433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    diff_img = png.from_array(diff, mode='RGB')
21533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    output = cStringIO.StringIO()
21633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    try:
21733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      diff_img.save(output)
21833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      diff = Bitmap.FromPng(output.getvalue())
21933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    finally:
22033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      output.close()
22133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
22233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return diff
22333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
22433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def GetBoundingBox(self, color, tolerance=0):
22533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return self._PrepareTools().BoundingBox(color, tolerance)
22633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
22733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def Crop(self, left, top, width, height):
22833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    cur_box = self._crop_box or (0, 0, self._width, self._height)
22933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    cur_left, cur_top, cur_width, cur_height = cur_box
23033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
23133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if (left < 0 or top < 0 or
23233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        (left + width) > cur_width or
23333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        (top + height) > cur_height):
23433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      raise ValueError('Invalid dimensions')
23533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
23633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._crop_box = cur_left + left, cur_top + top, width, height
23733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return self
23833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
23933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def ColorHistogram(self, ignore_color=None, tolerance=0):
24033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return self._PrepareTools().Histogram(ignore_color, tolerance)
241