1# Lint as: python2, python3 2# Copyright 2015 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6import errno 7from PIL import Image 8import logging 9import subprocess 10import tempfile 11 12from autotest_lib.client.cros.image_comparison import comparison_result 13from autotest_lib.client.cros.video import method_logger 14 15 16class PdiffImageComparer(object): 17 """ 18 Compares two images using ChromeOS' perceptualdiff binary. 19 20 """ 21 22 @method_logger.log 23 def compare(self, golden_img_path, test_img_path, box=None): 24 """ 25 Compares a test image against the specified golden image using the 26 terminal tool 'perceptualdiff'. 27 28 @param golden_img_path: path, complete path to a golden image. 29 @param test_img_path: path, complete path to a test image. 30 @param box: int tuple, left, upper, right, lower pixel coordinates. 31 Defines the rectangular boundary within which to compare. 32 @return: int, number of pixels that are different. 33 @raise : Whatever _pdiff_compare raises. 34 35 """ 36 if not box: 37 return self._pdiff_compare(golden_img_path, test_img_path) 38 39 ext = '.png' 40 tmp_golden_img_file = tempfile.NamedTemporaryFile(suffix=ext) 41 tmp_test_img_file = tempfile.NamedTemporaryFile(suffix=ext) 42 43 with tmp_golden_img_file, tmp_test_img_file: 44 tmp_golden_img_path = tmp_golden_img_file.name 45 tmp_test_img_path = tmp_test_img_file.name 46 47 Image.open(golden_img_path).crop(box).save(tmp_golden_img_path) 48 Image.open(test_img_path).crop(box).save(tmp_test_img_path) 49 50 return self._pdiff_compare(tmp_golden_img_path, tmp_test_img_path) 51 52 53 def _pdiff_compare(self, golden_img_path, test_img_path): 54 """ 55 Invokes perceptualdiff using subprocess tools. 56 57 @param golden_img_path: path, complete path to a golden image. 58 @param test_img_path: path, complete path to a test image. 59 @return: int, number of pixels that are different. 60 @raise ValueError if image dimensions are not the same. 61 @raise OSError: if file does not exist or can not be opened. 62 63 """ 64 65 # TODO mussa: Could downsampling the images be good for us? 66 67 tmp_diff_file = tempfile.NamedTemporaryFile(suffix='.png', delete=False) 68 args = ['perceptualdiff', golden_img_path, test_img_path, '-output', 69 tmp_diff_file.name] 70 71 logging.debug("Start process with args : " + str(args)) 72 73 p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 74 75 output = p.communicate() 76 logging.debug('output of perceptual diff command is') 77 logging.debug(output) 78 79 stdoutdata = output[0] 80 81 mismatch_error = "Image dimensions do not match" 82 diff_message = "Images are visibly different" 83 filenotfound_message = "Cannot open" 84 85 #TODO(dhaddock): Check for image not created 86 if p.returncode == 0: 87 # pdiff exited with 0, images were the same 88 return comparison_result.ComparisonResult(0, '', None) 89 90 if mismatch_error in stdoutdata: 91 raise ValueError("pdiff says: " + stdoutdata) 92 93 if diff_message in stdoutdata: 94 diff_pixels = [int(s) for s in stdoutdata.split() if s.isdigit()][0] 95 return comparison_result.ComparisonResult(int(diff_pixels), '', 96 tmp_diff_file.name) 97 98 if filenotfound_message in stdoutdata: 99 raise OSError(errno.ENOENT, "pdiff says: " + stdoutdata) 100 101 raise RuntimeError("Unknown result from pdiff: " 102 "Output : " + stdoutdata) 103 104 def __enter__(self): 105 return self 106 107 def __exit__(self, exc_type, exc_val, exc_tb): 108 pass 109