139 lines
5.7 KiB
Python
139 lines
5.7 KiB
Python
# Lint as: python2, python3
|
|
# Copyright 2014 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
"""Classes to do screen comparison."""
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
|
|
import logging
|
|
import os
|
|
import time
|
|
|
|
from PIL import ImageChops
|
|
from six.moves import range
|
|
|
|
|
|
class ScreenComparer(object):
|
|
"""A class to compare two screens.
|
|
|
|
Calling its member method compare() does the comparison.
|
|
|
|
"""
|
|
|
|
def __init__(self, capturer1, capturer2, output_dir, pixel_diff_margin,
|
|
wrong_pixels_margin, skip_if_diff_sizes=False):
|
|
"""Initializes the ScreenComparer objects.
|
|
|
|
@param capture1: The screen capturer object.
|
|
@param capture2: The screen capturer object.
|
|
@param output_dir: The directory for output images.
|
|
@param pixel_diff_margin: The margin for comparing a pixel. Only
|
|
if a pixel difference exceeds this margin, will treat as a wrong
|
|
pixel. Sets None means using default value by detecting
|
|
connector type.
|
|
@param wrong_pixels_margin: The percentage of margin for wrong pixels.
|
|
The value is in a closed interval [0.0, 1.0]. If the total
|
|
number of wrong pixels exceeds this margin, the check fails.
|
|
@param skip_if_diff_sizes: Skip the comparison if the image sizes are
|
|
different. Used in mirrored test as the internal and external
|
|
screens have different resolutions.
|
|
"""
|
|
# TODO(waihong): Support multiple capturers.
|
|
self._capturer1 = capturer1
|
|
self._capturer2 = capturer2
|
|
self._output_dir = output_dir
|
|
self._pixel_diff_margin = pixel_diff_margin
|
|
assert 0.0 <= wrong_pixels_margin <= 1.0
|
|
self._wrong_pixels_margin = wrong_pixels_margin
|
|
self._skip_if_diff_sizes = skip_if_diff_sizes
|
|
|
|
|
|
def compare(self):
|
|
"""Compares the screens.
|
|
|
|
@return: None if the check passes; otherwise, a string of error message.
|
|
"""
|
|
tags = [self._capturer1.TAG, self._capturer2.TAG]
|
|
images = [self._capturer1.capture(), self._capturer2.capture()]
|
|
|
|
if None in images:
|
|
message = ('Failed to capture the screen of %s.' %
|
|
tags[images.index(None)])
|
|
logging.error(message)
|
|
return message
|
|
|
|
# Sometimes the format of images got from X is not RGB,
|
|
# which may lead to ValueError raised by ImageChops.difference().
|
|
# So here we check the format before comparing them.
|
|
for i, image in enumerate(images):
|
|
if image.mode != 'RGB':
|
|
images[i] = image.convert('RGB')
|
|
|
|
message = 'Unexpected exception'
|
|
time_str = time.strftime('%H%M%S')
|
|
try:
|
|
# The size property is the resolution of the image.
|
|
if images[0].size != images[1].size:
|
|
message = ('Sizes of images %s and %s do not match: '
|
|
'%dx%d != %dx%d' %
|
|
(tuple(tags) + images[0].size + images[1].size))
|
|
if self._skip_if_diff_sizes:
|
|
logging.info(message)
|
|
return None
|
|
else:
|
|
logging.error(message)
|
|
return message
|
|
|
|
size = images[0].size[0] * images[0].size[1]
|
|
max_acceptable_wrong_pixels = int(self._wrong_pixels_margin * size)
|
|
|
|
logging.info('Comparing the images between %s and %s...', *tags)
|
|
diff_image = ImageChops.difference(*images)
|
|
histogram = diff_image.convert('L').histogram()
|
|
|
|
num_wrong_pixels = sum(histogram[self._pixel_diff_margin + 1:])
|
|
max_diff_value = max([x for x in range(len(histogram)) if histogram[x]])
|
|
if num_wrong_pixels > 0:
|
|
logging.debug('Histogram of difference: %r', histogram)
|
|
prefix_str = '%s-%dx%d' % ((time_str,) + images[0].size)
|
|
message = ('Result of %s: total %d wrong pixels '
|
|
'(diff up to %d)' % (
|
|
prefix_str, num_wrong_pixels, max_diff_value))
|
|
if num_wrong_pixels > max_acceptable_wrong_pixels:
|
|
logging.error(message)
|
|
return message
|
|
|
|
message += (', within the acceptable range %d' %
|
|
max_acceptable_wrong_pixels)
|
|
logging.warning(message)
|
|
else:
|
|
logging.info('Result: all pixels match (within +/- %d)',
|
|
max_diff_value)
|
|
message = None
|
|
return None
|
|
finally:
|
|
if message is not None:
|
|
for i in (0, 1):
|
|
# Use time and image size as the filename prefix.
|
|
prefix_str = '%s-%dx%d' % ((time_str,) + images[i].size)
|
|
# TODO(waihong): Save to a better lossless format.
|
|
file_path = os.path.join(
|
|
self._output_dir,
|
|
'%s-%s.png' % (prefix_str, tags[i]))
|
|
logging.info('Output the image %d to %s', i, file_path)
|
|
images[i].save(file_path)
|
|
|
|
file_path = os.path.join(
|
|
self._output_dir, '%s-diff.png' % prefix_str)
|
|
logging.info('Output the diff image to %s', file_path)
|
|
diff_image = ImageChops.difference(*images)
|
|
gray_image = diff_image.convert('L')
|
|
bw_image = gray_image.point(
|
|
lambda x: 0 if x <= self._pixel_diff_margin else 255,
|
|
'1')
|
|
bw_image.save(file_path)
|