25
25
26
26
import abc
27
27
import contextlib
28
+ from io import BytesIO
28
29
import itertools
29
30
import logging
30
31
import os
43
44
44
45
if six .PY2 :
45
46
from base64 import encodestring as encodebytes
46
- from cStringIO import StringIO as BytesIO
47
47
else :
48
48
from base64 import encodebytes
49
- from io import BytesIO
50
49
51
50
52
51
_log = logging .getLogger (__name__ )
@@ -575,6 +574,45 @@ def cleanup(self):
575
574
os .remove (fname )
576
575
577
576
577
+ @writers .register ('pillow' )
578
+ class PillowWriter (MovieWriter ):
579
+ @classmethod
580
+ def isAvailable (cls ):
581
+ try :
582
+ import PIL
583
+ except ImportError :
584
+ return False
585
+ return True
586
+
587
+ def __init__ (self , * args , ** kwargs ):
588
+ if kwargs .get ("extra_args" ) is None :
589
+ kwargs ["extra_args" ] = ()
590
+ super (PillowWriter , self ).__init__ (* args , ** kwargs )
591
+
592
+ def setup (self , fig , outfile , dpi = None ):
593
+ self ._frames = []
594
+ self ._outfile = outfile
595
+ self ._dpi = dpi
596
+ self ._fig = fig
597
+
598
+ def grab_frame (self , ** savefig_kwargs ):
599
+ from PIL import Image
600
+ buf = BytesIO ()
601
+ self ._fig .savefig (buf , ** dict (savefig_kwargs , format = "rgba" ))
602
+ renderer = self ._fig .canvas .get_renderer ()
603
+ # Using frombuffer / getbuffer may be slightly more efficient, but
604
+ # Py3-only.
605
+ self ._frames .append (Image .frombytes (
606
+ "RGBA" ,
607
+ (int (renderer .width ), int (renderer .height )),
608
+ buf .getvalue ()))
609
+
610
+ def finish (self ):
611
+ self ._frames [0 ].save (
612
+ self ._outfile , save_all = True , append_images = self ._frames [1 :],
613
+ duration = int (1000 / self .fps ))
614
+
615
+
578
616
# Base class of ffmpeg information. Has the config keys and the common set
579
617
# of arguments that controls the *output* side of things.
580
618
class FFMpegBase (object ):
@@ -1138,8 +1176,8 @@ class to use, such as 'ffmpeg'. If ``None``, defaults to
1138
1176
1139
1177
if 'bbox_inches' in savefig_kwargs :
1140
1178
_log .warning ("Warning: discarding the 'bbox_inches' argument in "
1141
- "'savefig_kwargs' as it may cause frame size "
1142
- "to vary, which is inappropriate for animation." )
1179
+ "'savefig_kwargs' as it may cause frame size "
1180
+ "to vary, which is inappropriate for animation." )
1143
1181
savefig_kwargs .pop ('bbox_inches' )
1144
1182
1145
1183
# Create a new sequence of frames for saved data. This is different
@@ -1150,16 +1188,15 @@ class to use, such as 'ffmpeg'. If ``None``, defaults to
1150
1188
# allow for this non-existent use case or find a way to make it work.
1151
1189
with rc_context ():
1152
1190
if rcParams ['savefig.bbox' ] == 'tight' :
1153
- _log .info ("Disabling savefig.bbox = 'tight', as it "
1154
- "may cause frame size to vary, which "
1155
- "is inappropriate for animation." )
1191
+ _log .info ("Disabling savefig.bbox = 'tight', as it may cause "
1192
+ " frame size to vary, which is inappropriate for "
1193
+ " animation." )
1156
1194
rcParams ['savefig.bbox' ] = None
1157
1195
with writer .saving (self ._fig , filename , dpi ):
1158
1196
for anim in all_anim :
1159
1197
# Clear the initial frame
1160
1198
anim ._init_draw ()
1161
- for data in zip (* [a .new_saved_frame_seq ()
1162
- for a in all_anim ]):
1199
+ for data in zip (* [a .new_saved_frame_seq () for a in all_anim ]):
1163
1200
for anim , d in zip (all_anim , data ):
1164
1201
# TODO: See if turning off blit is really necessary
1165
1202
anim ._draw_next_frame (d , blit = False )
0 commit comments