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
5import errno
6import Image
7import logging
8import subprocess
9import tempfile
10
11from autotest_lib.client.cros.image_comparison import comparison_result
12from autotest_lib.client.cros.video import method_logger
13
14
15class 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