Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9276eea

Browse files
committed
Inkscape shell mode.
1 parent 8a77cfb commit 9276eea

File tree

1 file changed

+78
-5
lines changed

1 file changed

+78
-5
lines changed

lib/matplotlib/testing/compare.py

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,17 @@
77

88
import six
99

10+
import atexit
11+
import functools
1012
import hashlib
13+
import itertools
1114
import os
1215
import shutil
16+
import sys
17+
try:
18+
from shlex import quote as _shlex_quote
19+
except ImportError:
20+
from pipes import quote as _shlex_quote
1321

1422
import numpy as np
1523

@@ -128,6 +136,74 @@ def convert(old, new):
128136
return convert
129137

130138

139+
class _SVGConverter(object):
140+
def __init__(self):
141+
self._proc = None
142+
# We cannot rely on the GC to trigger `__del__` at exit because
143+
# other modules (e.g. `subprocess`) may already have their globals
144+
# set to `None`, which make `proc.communicate` or `proc.terminate`
145+
# fail. By relying on `atexit` we ensure the destructor runs before
146+
# `None`-setting occurs.
147+
atexit.register(self.__del__)
148+
149+
def _read_to_prompt(self):
150+
"""Did Inkscape reach the prompt without crashing?
151+
"""
152+
stream = iter(functools.partial(self._proc.stdout.read, 1), "")
153+
prompt = tuple("\n>")
154+
n = len(prompt)
155+
its = itertools.tee(stream, n)
156+
for i, it in enumerate(its):
157+
next(itertools.islice(it, i, i), None) # Advance `it` by `i`.
158+
while True:
159+
window = tuple(map(next, its))
160+
if len(window) != n:
161+
# Ran out of data -- one of the `next(it)` raised
162+
# StopIteration, so the tuple is shorter.
163+
return False
164+
if self._proc.poll():
165+
# Inkscape exited.
166+
return False
167+
if window == prompt:
168+
# Successfully read until prompt.
169+
return True
170+
171+
def __call__(self, orig, dest):
172+
if (not self._proc # First run.
173+
or self._proc.poll()): # Inkscape terminated, e.g. crashed.
174+
env = os.environ.copy()
175+
# If one passes e.g. a png file to Inkscape, it will try to
176+
# query the user for conversion options via a GUI (even with
177+
# `--without-gui`). Unsetting `DISPLAY` prevents this (and causes
178+
# GTK to crash and Inkscape to terminate, but that'll just be
179+
# reported as a regular exception below).
180+
env["DISPLAY"] = ""
181+
# Do not load any user options.
182+
env["INKSCAPE_PROFILE_DIR"] = os.devnull
183+
self._proc = subprocess.Popen(
184+
[str("inkscape"), "--without-gui", "--shell"],
185+
env=env, stdin=subprocess.PIPE,
186+
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
187+
universal_newlines=True)
188+
if not self._read_to_prompt():
189+
raise OSError("Failed to start Inkscape")
190+
# Inkscape uses glib's `g_shell_parse_argv`, which has a consistent
191+
# behavior across platforms.
192+
self._proc.stdin.write(
193+
"{} --export-png={}\n".format(*map(_shlex_quote, [orig, dest])))
194+
self._proc.stdin.flush()
195+
if not self._read_to_prompt():
196+
raise ImageComparisonFailure(self._proc.stderr.read())
197+
198+
def __del__(self):
199+
if self._proc:
200+
if self._proc.poll() is None: # Not exited yet.
201+
self._proc.communicate("quit\n")
202+
self._proc.wait()
203+
self._proc.stdin.close()
204+
self._proc.stdout.close()
205+
206+
131207
def _update_converter():
132208
gs, gs_v = matplotlib.checkdep_ghostscript()
133209
if gs_v is not None:
@@ -138,9 +214,7 @@ def cmd(old, new):
138214
converter['eps'] = make_external_conversion_command(cmd)
139215

140216
if matplotlib.checkdep_inkscape() is not None:
141-
def cmd(old, new):
142-
return [str('inkscape'), '-z', old, '--export-png', new]
143-
converter['svg'] = make_external_conversion_command(cmd)
217+
converter['svg'] = _SVGConverter()
144218

145219

146220
#: A dictionary that maps filename extensions to functions which
@@ -363,9 +437,8 @@ def save_diff_image(expected, actual, output):
363437
actual, actualImage, expected, expectedImage)
364438
expectedImage = np.array(expectedImage).astype(float)
365439
actualImage = np.array(actualImage).astype(float)
366-
assert expectedImage.ndim == actualImage.ndim
367440
assert expectedImage.shape == actualImage.shape
368-
absDiffImage = abs(expectedImage - actualImage)
441+
absDiffImage = np.abs(expectedImage - actualImage)
369442

370443
# expand differences in luminance domain
371444
absDiffImage *= 255 * 10

0 commit comments

Comments
 (0)