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