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

Skip to content

Commit 2a25286

Browse files
committed
Inkscape shell mode.
1 parent 8a77cfb commit 2a25286

File tree

1 file changed

+68
-5
lines changed

1 file changed

+68
-5
lines changed

lib/matplotlib/testing/compare.py

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

88
import six
99

10+
import atexit
11+
import functools
1012
import hashlib
13+
import itertools
1114
import os
15+
import shlex
1216
import shutil
17+
import sys
1318

1419
import numpy as np
1520

@@ -128,6 +133,67 @@ def convert(old, new):
128133
return convert
129134

130135

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

140206
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)
207+
converter['svg'] = _SVGConverter()
144208

145209

146210
#: A dictionary that maps filename extensions to functions which
@@ -363,9 +427,8 @@ def save_diff_image(expected, actual, output):
363427
actual, actualImage, expected, expectedImage)
364428
expectedImage = np.array(expectedImage).astype(float)
365429
actualImage = np.array(actualImage).astype(float)
366-
assert expectedImage.ndim == actualImage.ndim
367430
assert expectedImage.shape == actualImage.shape
368-
absDiffImage = abs(expectedImage - actualImage)
431+
absDiffImage = np.abs(expectedImage - actualImage)
369432

370433
# expand differences in luminance domain
371434
absDiffImage *= 255 * 10

0 commit comments

Comments
 (0)