7
7
8
8
import six
9
9
10
+ import atexit
11
+ import functools
10
12
import hashlib
13
+ import itertools
11
14
import os
12
15
import shutil
16
+ import sys
13
17
14
18
import numpy as np
15
19
@@ -128,6 +132,57 @@ def convert(old, new):
128
132
return convert
129
133
130
134
135
+ class _SVGConverter (object ):
136
+ def __init__ (self ):
137
+ self ._proc = None
138
+ # We cannot rely on the GC to trigger `__del__` at exit because
139
+ # other modules (e.g. `subprocess`) may already have their globals
140
+ # set to `None`, which make `proc.communicate` or `proc.terminate`
141
+ # fail. By relying on `atexit` we ensure the destructor runs before
142
+ # `None`-setting occurs.
143
+ atexit .register (self .__del__ )
144
+
145
+ def _read_to_terminator (self ):
146
+ stream = iter (functools .partial (self ._proc .stdout .read , 1 ), b"" )
147
+ terminator = (b"\n " , b">" )
148
+ n = len (terminator )
149
+ its = itertools .tee (stream , n )
150
+ for i , it in enumerate (its ):
151
+ next (itertools .islice (it , i , i ), None ) # Advance `it` by `i`.
152
+ while True :
153
+ window = tuple (map (next , its ))
154
+ if (len (window ) != n or window == terminator ):
155
+ # First case is to running out of data -- one of the `next(it)`
156
+ # raises StopIteration, so the tuple is shorter.
157
+ # Second case is to successful reading until terminator.
158
+ break
159
+
160
+ def __call__ (self , orig , dest ):
161
+ if (not self ._proc # First run.
162
+ or self ._proc .poll ()): # Inkscape terminated, e.g. crashed.
163
+ self ._proc = subprocess .Popen ([str ("inkscape" ), "--shell" ],
164
+ stdin = subprocess .PIPE ,
165
+ stdout = subprocess .PIPE )
166
+ self ._read_to_terminator ()
167
+ try :
168
+ fsencode = os .fsencode
169
+ except AttributeError : # Py2.
170
+ def fsencode (s ):
171
+ return s .encode (sys .getfilesystemencoding ())
172
+ self ._proc .stdin .write (
173
+ fsencode ("{} --export-png={}\n " .format (orig , dest )))
174
+ self ._proc .stdin .flush ()
175
+ self ._read_to_terminator ()
176
+
177
+ def __del__ (self ):
178
+ if self ._proc :
179
+ if self ._proc .poll () is None : # Not exited yet.
180
+ self ._proc .communicate (b"quit\n " )
181
+ self ._proc .wait ()
182
+ self ._proc .stdin .close ()
183
+ self ._proc .stdout .close ()
184
+
185
+
131
186
def _update_converter ():
132
187
gs , gs_v = matplotlib .checkdep_ghostscript ()
133
188
if gs_v is not None :
@@ -138,9 +193,7 @@ def cmd(old, new):
138
193
converter ['eps' ] = make_external_conversion_command (cmd )
139
194
140
195
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 )
196
+ converter ['svg' ] = _SVGConverter ()
144
197
145
198
146
199
#: A dictionary that maps filename extensions to functions which
@@ -363,9 +416,8 @@ def save_diff_image(expected, actual, output):
363
416
actual , actualImage , expected , expectedImage )
364
417
expectedImage = np .array (expectedImage ).astype (float )
365
418
actualImage = np .array (actualImage ).astype (float )
366
- assert expectedImage .ndim == actualImage .ndim
367
- assert expectedImage .shape == actualImage .shape
368
- absDiffImage = abs (expectedImage - actualImage )
419
+ assert expectedImage .shape [:2 ] == actualImage .shape [:2 ]
420
+ absDiffImage = np .abs (expectedImage [:, :, :3 ] - actualImage [:, :, :3 ])
369
421
370
422
# expand differences in luminance domain
371
423
absDiffImage *= 255 * 10
0 commit comments