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