Skip to content

Commit 57f02ad

Browse files
committed
Correctly handle high dpi in Pillow animation writer.
The idea is just to pass self.dpi to savefig() and then use self.frame_size as the frame size rather than looking up renderer internals, but this is made easier by properly moving some attributes and methods (some of `__init__`, `setup`, `frame_size`) from MovieWriter (which should really be called SubprocessMovieWriter, as that's what it does) to AbstractMovieWriter, so that PillowWriter can reuse them without having to carefully prevent any attempt of starting a subprocess.
1 parent e23b2aa commit 57f02ad

File tree

1 file changed

+37
-45
lines changed

1 file changed

+37
-45
lines changed

lib/matplotlib/animation.py

Lines changed: 37 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,10 @@ class AbstractMovieWriter(abc.ABC):
188188
``writer`` argument of `Animation.save()`.
189189
'''
190190

191+
def __init__(self, fps=5, metadata=None):
192+
self.fps = fps
193+
self.metadata = metadata if metadata is not None else {}
194+
191195
@abc.abstractmethod
192196
def setup(self, fig, outfile, dpi=None):
193197
'''
@@ -203,6 +207,17 @@ def setup(self, fig, outfile, dpi=None):
203207
The DPI (or resolution) for the file. This controls the size
204208
in pixels of the resulting movie file. Default is ``fig.dpi``.
205209
'''
210+
self.outfile = outfile
211+
self.fig = fig
212+
if dpi is None:
213+
dpi = self.fig.dpi
214+
self.dpi = dpi
215+
216+
@property
217+
def frame_size(self):
218+
'''A tuple ``(width, height)`` in pixels of a movie frame.'''
219+
w, h = self.fig.get_size_inches()
220+
return int(w * self.dpi), int(h * self.dpi)
206221

207222
@abc.abstractmethod
208223
def grab_frame(self, **savefig_kwargs):
@@ -275,7 +290,7 @@ def __init__(self, fps=5, codec=None, bitrate=None, extra_args=None,
275290
output file. Some keys that may be of use include:
276291
title, artist, genre, subject, copyright, srcform, comment.
277292
"""
278-
if self.__class__ is MovieWriter:
293+
if type(self) is MovieWriter:
279294
# TODO MovieWriter is still an abstract class and needs to be
280295
# extended with a mixin. This should be clearer in naming
281296
# and description. For now, just give a reasonable error
@@ -284,7 +299,8 @@ def __init__(self, fps=5, codec=None, bitrate=None, extra_args=None,
284299
'MovieWriter cannot be instantiated directly. Please use one '
285300
'of its subclasses.')
286301

287-
self.fps = fps
302+
super().__init__(fps=fps, metadata=metadata)
303+
288304
self.frame_format = 'rgba'
289305

290306
if codec is None:
@@ -302,17 +318,6 @@ def __init__(self, fps=5, codec=None, bitrate=None, extra_args=None,
302318
else:
303319
self.extra_args = extra_args
304320

305-
if metadata is None:
306-
self.metadata = dict()
307-
else:
308-
self.metadata = metadata
309-
310-
@property
311-
def frame_size(self):
312-
'''A tuple ``(width, height)`` in pixels of a movie frame.'''
313-
w, h = self.fig.get_size_inches()
314-
return int(w * self.dpi), int(h * self.dpi)
315-
316321
def _adjust_frame_size(self):
317322
if self.codec == 'h264':
318323
wo, ho = self.fig.get_size_inches()
@@ -340,13 +345,8 @@ def setup(self, fig, outfile, dpi=None):
340345
The DPI (or resolution) for the file. This controls the size
341346
in pixels of the resulting movie file. Default is fig.dpi.
342347
'''
343-
self.outfile = outfile
344-
self.fig = fig
345-
if dpi is None:
346-
dpi = self.fig.dpi
347-
self.dpi = dpi
348+
super().setup(fig, outfile, dpi=dpi)
348349
self._w, self._h = self._adjust_frame_size()
349-
350350
# Run here so that grab_frame() can write the data to a pipe. This
351351
# eliminates the need for temp files.
352352
self._run()
@@ -540,35 +540,27 @@ def cleanup(self):
540540

541541

542542
@writers.register('pillow')
543-
class PillowWriter(MovieWriter):
543+
class PillowWriter(AbstractMovieWriter):
544544
@classmethod
545545
def isAvailable(cls):
546546
return True
547547

548-
def __init__(self, *args, **kwargs):
549-
if kwargs.get("extra_args") is None:
550-
kwargs["extra_args"] = ()
551-
super().__init__(*args, **kwargs)
552-
553548
def setup(self, fig, outfile, dpi=None):
549+
super().setup(fig, outfile, dpi=dpi)
554550
self._frames = []
555-
self._outfile = outfile
556-
self._dpi = dpi
557-
self._fig = fig
558551

559552
def grab_frame(self, **savefig_kwargs):
560553
from PIL import Image
561554
buf = BytesIO()
562-
self._fig.savefig(buf, **dict(savefig_kwargs, format="rgba"))
563-
renderer = self._fig.canvas.get_renderer()
555+
self.fig.savefig(
556+
buf, **{**savefig_kwargs, "format": "rgba", "dpi": self.dpi})
557+
renderer = self.fig.canvas.get_renderer()
564558
self._frames.append(Image.frombuffer(
565-
"RGBA",
566-
(int(renderer.width), int(renderer.height)), buf.getbuffer(),
567-
"raw", "RGBA", 0, 1))
559+
"RGBA", self.frame_size, buf.getbuffer(), "raw", "RGBA", 0, 1))
568560

569561
def finish(self):
570562
self._frames[0].save(
571-
self._outfile, save_all=True, append_images=self._frames[1:],
563+
self.outfile, save_all=True, append_images=self._frames[1:],
572564
duration=int(1000 / self.fps), loop=0)
573565

574566

@@ -1075,11 +1067,15 @@ def func(current_frame: int, total_frames: int) -> Any
10751067
if dpi == 'figure':
10761068
dpi = self._fig.dpi
10771069

1078-
if codec is None:
1079-
codec = mpl.rcParams['animation.codec']
1080-
1081-
if bitrate is None:
1082-
bitrate = mpl.rcParams['animation.bitrate']
1070+
writer_kwargs = {}
1071+
if codec is not None:
1072+
writer_kwargs['codec'] = codec
1073+
if bitrate is not None:
1074+
writer_kwargs['bitrate'] = bitrate
1075+
if extra_args is not None:
1076+
writer_kwargs['extra_args'] = extra_args
1077+
if metadata is not None:
1078+
writer_kwargs['metadata'] = metadata
10831079

10841080
all_anim = [self]
10851081
if extra_anim is not None:
@@ -1091,9 +1087,7 @@ def func(current_frame: int, total_frames: int) -> Any
10911087
# registered class.
10921088
if isinstance(writer, str):
10931089
if writers.is_available(writer):
1094-
writer = writers[writer](fps, codec, bitrate,
1095-
extra_args=extra_args,
1096-
metadata=metadata)
1090+
writer = writers[writer](fps, **writer_kwargs)
10971091
else:
10981092
alt_writer = next(writers, None)
10991093
if alt_writer is None:
@@ -1102,9 +1096,7 @@ def func(current_frame: int, total_frames: int) -> Any
11021096
"save animations.")
11031097
_log.warning("MovieWriter %s unavailable; trying to use %s "
11041098
"instead.", writer, alt_writer)
1105-
writer = alt_writer(
1106-
fps, codec, bitrate,
1107-
extra_args=extra_args, metadata=metadata)
1099+
writer = alt_writer(fps, **writer_kwargs)
11081100
_log.info('Animation.save using %s', type(writer))
11091101

11101102
if 'bbox_inches' in savefig_kwargs:

0 commit comments

Comments
 (0)