1c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch# Copyright 2013 The Chromium Authors. All rights reserved. 2c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch# Use of this source code is governed by a BSD-style license that can be 3c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch# found in the LICENSE file. 4c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 5c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch"""Utilities for performing pixel-by-pixel image comparision.""" 6c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 7c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochimport itertools 8c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochimport StringIO 9c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochfrom PIL import Image 10c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 11c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 12c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochdef _AreTheSameSize(images): 13c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch """Returns whether a set of images are the size size. 14c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 15c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Args: 16c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch images: a list of images to compare. 17c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 18c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Returns: 19c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch boolean. 20c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 21c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Raises: 22c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Exception: One image or fewer is passed in. 23c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch """ 24c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch if len(images) > 1: 25c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch return all(images[0].size == img.size for img in images[1:]) 26c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch else: 27c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch raise Exception('No images passed in.') 28c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 29c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 30c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochdef _GetDifferenceWithMask(image1, image2, mask=None, 31c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch masked_color=(225, 225, 225, 255), 32c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch same_color=(255, 255, 255, 255), 33c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch different_color=(210, 0, 0, 255)): 34c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch """Returns an image representing the difference between the two images. 35c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 36c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch This function computes the difference between two images taking into 37c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch account a mask if it is provided. The final three arguments represent 38c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch the coloration of the generated image. 39c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 40c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Args: 41c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image1: the first image to compare. 42c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image2: the second image to compare. 43c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch mask: an optional mask image consisting of only black and white pixels 44c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch where white pixels indicate the portion of the image to be masked out. 45c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch masked_color: the color of a masked section in the resulting image. 46c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch same_color: the color of an unmasked section that is the same. 47c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch between images 1 and 2 in the resulting image. 48c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch different_color: the color of an unmasked section that is different 49c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch between images 1 and 2 in the resulting image. 50c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 51c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Returns: 52c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch A 2-tuple with an image representing the unmasked difference between the 53c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch two input images and the number of different pixels. 54c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 55c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Raises: 56c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Exception: if image1, image2, and mask are not the same size. 57c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch """ 58c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image_mask = mask 59c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch if not mask: 60c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image_mask = Image.new('RGBA', image1.size, (0, 0, 0, 255)) 61c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch if not _AreTheSameSize([image1, image2, image_mask]): 62c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch raise Exception('images and mask must be the same size.') 63c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image_diff = Image.new('RGBA', image1.size, (0, 0, 0, 255)) 64c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch data = [] 65c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch diff_pixels = 0 66c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch for m, px1, px2 in itertools.izip(image_mask.getdata(), 67c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image1.getdata(), 68c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image2.getdata()): 69c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch if m == (255, 255, 255, 255): 70c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch data.append(masked_color) 71c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch elif px1 == px2: 72c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch data.append(same_color) 73c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch else: 74c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch data.append(different_color) 75c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch diff_pixels += 1 76c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 77c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image_diff.putdata(data) 78c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch return (image_diff, diff_pixels) 79c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 80c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 81c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochdef CreateMask(images): 82c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch """Computes a mask for a set of images. 83c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 84c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Returns a difference mask that is computed from the images 85c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch which are passed in. The mask will have a white pixel 86c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch anywhere that the input images differ and a black pixel 87c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch everywhere else. 88c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 89c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Args: 90c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch images: list of images to compute the mask from. 91c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 92c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Returns: 93c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch an image of only black and white pixels where white pixels represent 94c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch areas in the input images that have differences. 95c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 96c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Raises: 97c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Exception: if the images passed in are not of the same size. 98c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Exception: if fewer than one image is passed in. 99c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch """ 100c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch if not images: 101c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch raise Exception('mask must be created from one or more images.') 102c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch mask = Image.new('RGBA', images[0].size, (0, 0, 0, 255)) 103c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image = images[0] 104c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch for other_image in images[1:]: 105c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch mask = _GetDifferenceWithMask( 106c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image, 107c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch other_image, 108c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch mask, 109c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch masked_color=(255, 255, 255, 255), 110c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch same_color=(0, 0, 0, 255), 111c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch different_color=(255, 255, 255, 255))[0] 112c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch return mask 113c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 114c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 115c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochdef AddMasks(masks): 116c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch """Combines a list of mask images into one mask image. 117c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 118c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Args: 119c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch masks: a list of mask-images. 120c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 121c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Returns: 122c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch a new mask that represents the sum of the masked 123c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch regions of the passed in list of mask-images. 124c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 125c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Raises: 126c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Exception: if masks is an empty list, or if masks are not the same size. 127c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch """ 128c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch if not masks: 129c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch raise Exception('masks must be a list containing at least one image.') 130c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch if len(masks) > 1 and not _AreTheSameSize(masks): 131c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch raise Exception('masks in list must be of the same size.') 132c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch white = (255, 255, 255, 255) 133c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch black = (0, 0, 0, 255) 134c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch masks_data = [mask.getdata() for mask in masks] 135c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image = Image.new('RGBA', masks[0].size, black) 136c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image.putdata([white if white in px_set else black 137c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch for px_set in itertools.izip(*masks_data)]) 138c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch return image 139c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 140c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 141c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochdef ConvertDiffToMask(diff): 142c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch """Converts a Diff image into a Mask image. 143c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 144c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Args: 145c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch diff: the diff image to convert. 146c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 147c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Returns: 148c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch a new mask image where everything that was masked or different in the diff 149c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch is now masked. 150c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch """ 151c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch white = (255, 255, 255, 255) 152c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch black = (0, 0, 0, 255) 153c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch diff_data = diff.getdata() 154c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image = Image.new('RGBA', diff.size, black) 155c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image.putdata([black if px == white else white for px in diff_data]) 156c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch return image 157c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 158c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 159c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochdef VisualizeImageDifferences(image1, image2, mask=None): 160c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch """Returns an image repesenting the unmasked differences between two images. 161c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 162c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Iterates through the pixel values of two images and an optional 163c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch mask. If the pixel values are the same, or the pixel is masked, 164c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch (0,0,0) is stored for that pixel. Otherwise, (255,255,255) is stored. 165c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch This ultimately produces an image where unmasked differences between 166c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch the two images are white pixels, and everything else is black. 167c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 168c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Args: 169c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image1: an RGB image 170c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image2: another RGB image of the same size as image1. 171c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch mask: an optional RGB image consisting of only white and black pixels 172c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch where the white pixels represent the parts of the images to be masked 173c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch out. 174c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 175c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Returns: 176c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch A 2-tuple with an image representing the unmasked difference between the 177c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch two input images and the number of different pixels. 178c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 179c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Raises: 180c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Exception: if the two images and optional mask are different sizes. 181c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch """ 182c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch return _GetDifferenceWithMask(image1, image2, mask) 183c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 184c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 185c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochdef InflateMask(image, passes): 186c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch """A function that adds layers of pixels around the white edges of a mask. 187c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 188c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch This function evaluates a 'frontier' of valid pixels indices. Initially, 189c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch this frontier contains all indices in the image. However, with each pass 190c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch only the pixels' indices which were added to the mask by inflation 191c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch are added to the next pass's frontier. This gives the algorithm a 192c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch large upfront cost that scales negligably when the number of passes 193c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch is increased. 194c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 195c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Args: 196c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image: the RGBA PIL.Image mask to inflate. 197c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch passes: the number of passes to inflate the image by. 198c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 199c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Returns: 200c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch A RGBA PIL.Image. 201c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch """ 202c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch inflated = Image.new('RGBA', image.size) 203c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch new_dataset = list(image.getdata()) 204c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch old_dataset = list(image.getdata()) 205c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 206c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch frontier = set(range(len(old_dataset))) 207c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch new_frontier = set() 208c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 209c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch l = [-1, 1] 210c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 211c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch def _ShadeHorizontal(index, px): 212c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch col = index % image.size[0] 213c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch if px == (255, 255, 255, 255): 214c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch for x in l: 215c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch if 0 <= col + x < image.size[0]: 216c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch if old_dataset[index + x] != (255, 255, 255, 255): 217c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch new_frontier.add(index + x) 218c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch new_dataset[index + x] = (255, 255, 255, 255) 219c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 220c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch def _ShadeVertical(index, px): 221c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch row = index / image.size[0] 222c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch if px == (255, 255, 255, 255): 223c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch for x in l: 224c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch if 0 <= row + x < image.size[1]: 225c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch if old_dataset[index + image.size[0] * x] != (255, 255, 255, 255): 226c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch new_frontier.add(index + image.size[0] * x) 227c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch new_dataset[index + image.size[0] * x] = (255, 255, 255, 255) 228c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 229c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch for _ in range(passes): 230c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch for index in frontier: 231c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch _ShadeHorizontal(index, old_dataset[index]) 232c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch _ShadeVertical(index, old_dataset[index]) 233c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch old_dataset, new_dataset = new_dataset, new_dataset 234c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch frontier, new_frontier = new_frontier, set() 235c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch inflated.putdata(new_dataset) 236c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch return inflated 237c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 238c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 239c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochdef TotalDifferentPixels(image1, image2, mask=None): 240c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch """Computes the number of different pixels between two images. 241c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 242c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Args: 243c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image1: the first RGB image to be compared. 244c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image2: the second RGB image to be compared. 245c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch mask: an optional RGB image of only black and white pixels 246c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch where white pixels indicate the parts of the image to be masked out. 247c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 248c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Returns: 249c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch the number of differing pixels between the images. 250c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 251c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Raises: 252c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Exception: if the images to be compared and the mask are not the same size. 253c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch """ 254c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image_mask = mask 255c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch if not mask: 256c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image_mask = Image.new('RGBA', image1.size, (0, 0, 0, 255)) 257c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch if _AreTheSameSize([image1, image2, image_mask]): 258c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch total_diff = 0 259c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch for px1, px2, m in itertools.izip(image1.getdata(), 260c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image2.getdata(), 261c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image_mask.getdata()): 262c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch if m == (255, 255, 255, 255): 263c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch continue 264c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch elif px1 != px2: 265c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch total_diff += 1 266c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch else: 267c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch continue 268c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch return total_diff 269c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch else: 270c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch raise Exception('images and mask must be the same size') 271c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 272c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 273c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochdef SameImage(image1, image2, mask=None): 274c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch """Returns a boolean representing whether the images are the same. 275c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 276c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Returns a boolean indicating whether two images are similar 277c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch enough to be considered the same. Essentially wraps the 278c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch TotalDifferentPixels function. 279c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 280c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 281c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Args: 282c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image1: an RGB image to compare. 283c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image2: an RGB image to compare. 284c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch mask: an optional image of only black and white pixels 285c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch where white pixels are masked out 286c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 287c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Returns: 288c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch True if the images are similar, False otherwise. 289c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 290c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Raises: 291c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Exception: if the images (and mask) are different sizes. 292c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch """ 293c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch different_pixels = TotalDifferentPixels(image1, image2, mask) 294c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch return different_pixels == 0 295c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 296c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 297c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochdef EncodePNG(image): 298c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch """Returns the PNG file-contents of the image. 299c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 300c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Args: 301c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image: an RGB image to be encoded. 302c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 303c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Returns: 304c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch a base64 encoded string representing the image. 305c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch """ 306c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch f = StringIO.StringIO() 307c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch image.save(f, 'PNG') 308c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch encoded_image = f.getvalue() 309c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch f.close() 310c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch return encoded_image 311c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 312c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 313c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochdef DecodePNG(png): 314c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch """Returns a RGB image from PNG file-contents. 315c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 316c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Args: 317c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch encoded_image: PNG file-contents of an RGB image. 318c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 319c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch Returns: 320c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch an RGB image 321c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch """ 322c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch return Image.open(StringIO.StringIO(png)) 323