[diffoscope] 01/06: Add visual comparison for JPEG and ICO images.
Maria Glukhova
siamezzze-guest at moszumanska.debian.org
Sat Apr 29 17:16:58 UTC 2017
This is an automated email from the git hooks/post-receive script.
siamezzze-guest pushed a commit to branch siamezzze/image-visual-comparison
in repository diffoscope.
commit ab33d348e0dce7b5d14bbc955c4d6cfbfb9e622b
Author: Maria Glukhova <siamezzze at gmail.com>
Date: Sat Apr 1 22:07:54 2017 +0300
Add visual comparison for JPEG and ICO images.
Add VisualDifference class and .visuals field to Difference class.
If these are present, HTML presenter will output them instead of
text-based difference.
Add pixel difference and flicker (animation) difference for JPEG and
ICO files.
---
diffoscope/comparators/image.py | 66 ++++++++++++++++++++++++++++++++++++--
diffoscope/difference.py | 29 +++++++++++++++++
diffoscope/external_tools.py | 4 +++
diffoscope/presenters/html/html.py | 23 ++++++++++++-
4 files changed, 118 insertions(+), 4 deletions(-)
diff --git a/diffoscope/comparators/image.py b/diffoscope/comparators/image.py
index 2e78c98..68e53d9 100644
--- a/diffoscope/comparators/image.py
+++ b/diffoscope/comparators/image.py
@@ -19,10 +19,11 @@
import re
import subprocess
+import base64
from diffoscope.tools import tool_required
from diffoscope.tempfiles import get_named_temporary_file
-from diffoscope.difference import Difference
+from diffoscope.difference import Difference, VisualDifference
from .utils.file import File
from .utils.command import Command
@@ -77,12 +78,58 @@ class Identify(Command):
self.path,
]
+ at tool_required('compare')
+def pixel_difference(image1_path, image2_path):
+ compared_filename = get_named_temporary_file(suffix='.png').name
+ try:
+ subprocess.check_call(('compare', image1_path, image2_path,
+ '-compose', 'src', compared_filename))
+ except subprocess.CalledProcessError as e:
+ # ImageMagick's `compare` will return 1 if images are different
+ if e.returncode == 1:
+ pass
+ content = base64.b64encode(open(compared_filename, 'rb').read())
+ content = content.decode('utf8')
+ datatype = 'image/png;base64'
+ result = VisualDifference(datatype, content, "Pixel difference")
+ return result
+
+ at tool_required('convert')
+def flicker_difference(image1_path, image2_path):
+ compared_filename = get_named_temporary_file(suffix='.gif').name
+ subprocess.check_call(
+ ('convert', '-delay', '50', image1_path, image2_path,
+ '-loop', '0', '-compose', 'difference', compared_filename))
+ content = base64.b64encode(open(compared_filename, 'rb').read())
+ content = content.decode('utf8')
+ datatype = 'image/gif;base64'
+ result = VisualDifference(datatype, content, "Flicker difference")
+ return result
+
+ at tool_required('identify')
+def get_image_size(image_path):
+ return subprocess.check_output(('identify', '-format',
+ '%[h]x%[w]', image_path))
+
class JPEGImageFile(File):
RE_FILE_TYPE = re.compile(r'\bJPEG image data\b')
def compare_details(self, other, source=None):
+ content_diff = Difference.from_command(Img2Txt, self.path, other.path,
+ source='Image content')
+ if content_diff is not None:
+ try:
+ own_size = get_image_size(self.path)
+ other_size = get_image_size(other.path)
+ if own_size == other_size:
+ content_diff.add_visuals([
+ pixel_difference(self.path, other.path),
+ flicker_difference(self.path, other.path)
+ ])
+ except subprocess.CalledProcessError: # noqa
+ pass
return [
- Difference.from_command(Img2Txt, self.path, other.path),
+ content_diff,
Difference.from_command(
Identify,
self.path,
@@ -103,7 +150,20 @@ class ICOImageFile(File):
except subprocess.CalledProcessError: # noqa
pass
else:
- differences.append(Difference.from_command(Img2Txt, png_a, png_b))
+ content_diff = Difference.from_command(Img2Txt, png_a, png_b,
+ source='Image content')
+ if content_diff is not None:
+ try:
+ own_size = get_image_size(self.path)
+ other_size = get_image_size(other.path)
+ if own_size == other_size:
+ content_diff.add_visuals([
+ pixel_difference(self.path, other.path),
+ flicker_difference(self.path, other.path)
+ ])
+ except subprocess.CalledProcessError: # noqa
+ pass
+ differences.append(content_diff)
differences.append(Difference.from_command(
Identify,
diff --git a/diffoscope/difference.py b/diffoscope/difference.py
index 8342cc0..53e1dda 100644
--- a/diffoscope/difference.py
+++ b/diffoscope/difference.py
@@ -32,6 +32,25 @@ DIFF_CHUNK = 4096
logger = logging.getLogger(__name__)
+class VisualDifference(object):
+ def __init__(self, data_type, content, source):
+ self._data_type = data_type
+ self._content = content
+ self._source = source
+
+ @property
+ def data_type(self):
+ return self._data_type
+
+ @property
+ def content(self):
+ return self._content
+
+ @property
+ def source(self):
+ return self._source
+
+
class Difference(object):
def __init__(self, unified_diff, path1, path2, source=None, comment=None, has_internal_linenos=False):
self._comments = []
@@ -60,6 +79,7 @@ class Difference(object):
# Whether the unified_diff already contains line numbers inside itself
self._has_internal_linenos = has_internal_linenos
self._details = []
+ self._visuals = []
def __repr__(self):
return '<Difference %s -- %s %s>' % (self._source1, self._source2, self._details)
@@ -160,11 +180,20 @@ class Difference(object):
def details(self):
return self._details
+ @property
+ def visuals(self):
+ return self._visuals
+
def add_details(self, differences):
if len([d for d in differences if type(d) is not Difference]) > 0:
raise TypeError("'differences' must contains Difference objects'")
self._details.extend(differences)
+ def add_visuals(self, visuals):
+ if any([type(v) is not VisualDifference for v in visuals]):
+ raise TypeError("'visuals' must contain VisualDifference objects'")
+ self._visuals.extend(visuals)
+
def get_reverse(self):
if self._unified_diff is None:
unified_diff = None
diff --git a/diffoscope/external_tools.py b/diffoscope/external_tools.py
index fd1173e..34f1ca7 100644
--- a/diffoscope/external_tools.py
+++ b/diffoscope/external_tools.py
@@ -42,6 +42,10 @@ EXTERNAL_TOOLS = {
'debian': 'diffutils',
'arch': 'diffutils',
},
+ 'compare': {
+ 'debian': 'imagemagick',
+ 'arch': 'imagemagick',
+ },
'cpio': {
'debian': 'cpio',
'arch': 'cpio',
diff --git a/diffoscope/presenters/html/html.py b/diffoscope/presenters/html/html.py
index bf8e049..a8e3804 100644
--- a/diffoscope/presenters/html/html.py
+++ b/diffoscope/presenters/html/html.py
@@ -423,6 +423,24 @@ def output_unified_diff(print_func, css_url, directory, unified_diff, has_intern
text = "load diff (%s %s%s)" % (spl_current_page, noun, (", truncated" if truncated else ""))
print_func(templates.UD_TABLE_FOOTER % {"filename": html.escape("%s-1.html" % mainname), "text": text}, force=True)
+def output_visual(print_func, visual, parents):
+ logger.debug('visual difference for %s', visual.source)
+ sources = parents + [visual.source]
+ print_func(u'<div class="difference">')
+ print_func(u'<div class="diffheader">')
+ print_func(u'<div class="diffcontrol">[−]</div>')
+ print_func(u'<div><span class="source">%s</span>'
+ % html.escape(visual.source))
+ anchor = escape_anchor('/'.join(sources[1:]))
+ print_func(
+ u' <a class="anchor" href="#%s" name="%s">\xb6</a>' % (anchor, anchor))
+ print_func(u"</div>")
+ print_func(u"</div>")
+ print_func(u'<div class="difference">'
+ u'<img src=\"data:%s,%s\" alt=\"compared images\" /></div>' %
+ (visual.data_type, visual.content))
+ print_func(u"</div>", force=True)
+
def escape_anchor(val):
"""
ID and NAME tokens must begin with a letter ([A-Za-z]) and may be followed
@@ -461,7 +479,10 @@ def output_difference(difference, print_func, css_url, directory, parents):
print_func(u'<div class="comment">%s</div>'
% u'<br />'.join(map(html.escape, difference.comments)))
print_func(u"</div>")
- if difference.unified_diff:
+ if difference.unified_diff and len(difference.visuals) > 0:
+ for visual in difference.visuals:
+ output_visual(print_func, visual, sources)
+ elif difference.unified_diff:
output_unified_diff(print_func, css_url, directory, difference.unified_diff, difference.has_internal_linenos)
for detail in difference.details:
output_difference(detail, print_func, css_url, directory, sources)
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/reproducible/diffoscope.git
More information about the Reproducible-commits
mailing list