10000 Merge pull request #29024 from QuLogic/animation-fixes · QuLogic/matplotlib@6a8211e · GitHub
[go: up one dir, main page]

Skip to content

Commit 6a8211e

Browse files
authored
Merge pull request matplotlib#29024 from QuLogic/animation-fixes
Fix saving animations to transparent formats
2 parents 8f3b029 + 3fa8b10 commit 6a8211e

File tree

1 file changed

+47
-10
lines changed

1 file changed

+47
-10
lines changed

lib/matplotlib/animation.py

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,14 @@ def frame_size(self):
178178
w, h = self.fig.get_size_inches()
179179
return int(w * self.dpi), int(h * self.dpi)
180180

181+
def _supports_transparency(self):
182+
"""
183+
Whether this writer supports transparency.
184+
185+
Writers may consult output file type and codec to determine this at runtime.
186+
"""
187+
return False
188+
181189
@abc.abstractmethod
182190
def grab_frame(self, **savefig_kwargs):
183191
"""
@@ -468,6 +476,9 @@ def finish(self):
468476

469477
@writers.register('pillow')
470478
class PillowWriter(AbstractMovieWriter):
479+
def _supports_transparency(self):
480+
return True
481+
471482
@classmethod
472483
def isAvailable(cls):
473484
return True
@@ -503,11 +514,26 @@ class FFMpegBase:
503514
_exec_key = 'animation.ffmpeg_path'
504515
_args_key = 'animation.ffmpeg_args'
505516

517+
def _supports_transparency(self):
518+
suffix = Path(self.outfile).suffix
519+
if suffix in {'.apng', '.avif', '.gif', '.webm', '.webp'}:
520+
return True
521+
# This list was found by going through `ffmpeg -codecs` for video encoders,
522+
# running them with _support_transparency() forced to True, and checking that
523+
# the "Pixel format" in Kdenlive included alpha. Note this is not a guarantee
524+
# that transparency will work; you may also need to pass `-pix_fmt`, but we
525+
# trust the user has done so if they are asking for these formats.
526+
return self.codec in {
527+
'apng', 'avrp' 10000 , 'bmp', 'cfhd', 'dpx', 'ffv1', 'ffvhuff', 'gif', 'huffyuv',
528+
'jpeg2000', 'ljpeg', 'png', 'prores', 'prores_aw', 'prores_ks', 'qtrle',
529+
'rawvideo', 'targa', 'tiff', 'utvideo', 'v408', }
530+
506531
@property
507532
def output_args(self):
508533
args = []
509-
if Path(self.outfile).suffix == '.gif':
510-
self.codec = 'gif'
534+
suffix = Path(self.outfile).suffix
535+
if suffix in {'.apng', '.avif', '.gif', '.webm', '.webp'}:
536+
self.codec = suffix[1:]
511537
else:
512538
args.extend(['-vcodec', self.codec])
513539
extra_args = (self.extra_args if self.extra_args is not None
@@ -518,11 +544,17 @@ def output_args(self):
518544
# macOS). Also fixes internet explorer. This is as of 2015/10/29.
519545
if self.codec == 'h264' and '-pix_fmt' not in extra_args:
520546
args.extend(['-pix_fmt', 'yuv420p'])
521-
# For GIF, we're telling FFMPEG to split the video stream, to generate
547+
# For GIF, we're telling FFmpeg to split the video stream, to generate
522548
# a palette, and then use it for encoding.
523549
elif self.codec == 'gif' and '-filter_complex' not in extra_args:
524550
args.extend(['-filter_complex',
525551
'split [a][b];[a] palettegen [p];[b][p] paletteuse'])
552+
# For AVIF, we're telling FFmpeg to split the video stream, extract the alpha,
553+
# in order to place it in a secondary stream, as needed by AVIF-in-FFmpeg.
554+
elif self.codec == 'avif' and '-filter_complex' not in extra_args:
555+
args.extend(['-filter_complex',
556+
'split [rgb][rgba]; [rgba] alphaextract [alpha]',
557+
'-map', '[rgb]', '-map', '[alpha]'])
526558
if self.bitrate > 0:
527559
args.extend(['-b', '%dk' % self.bitrate]) # %dk: bitrate in kbps.
528560
for k, v in self.metadata.items():
@@ -610,6 +642,10 @@ class ImageMagickBase:
610642
_exec_key = 'animation.convert_path'
611643
_args_key = 'animation.convert_args'
612644

645+
def _supports_transparency(self):
646+
suffix = Path(self.outfile).suffix
647+
return suffix in {'.apng', '.avif', '.gif', '.webm', '.webp'}
648+
613649
def _args(self):
614650
# ImageMagick does not recognize "raw".
615651
fmt = "rgba" if self.frame_format == "raw" else self.frame_format
@@ -1045,22 +1081,23 @@ def func(current_frame: int, total_frames: int) -> Any
10451081
# since GUI widgets are gone. Either need to remove extra code to
10461082
# allow for this non-existent use case or find a way to make it work.
10471083

1048-
facecolor = savefig_kwargs.get('facecolor',
1049-
mpl.rcParams['savefig.facecolor'])
1050-
if facecolor == 'auto':
1051-
facecolor = self._fig.get_facecolor()
1052-
10531084
def _pre_composite_to_white(color):
10541085
r, g, b, a = mcolors.to_rgba(color)
10551086
return a * np.array([r, g, b]) + 1 - a
10561087

1057-
savefig_kwargs['facecolor'] = _pre_composite_to_white(facecolor)
1058-
savefig_kwargs['transparent'] = False # just to be safe!
10591088
# canvas._is_saving = True makes the draw_event animation-starting
10601089
# callback a no-op; canvas.manager = None prevents resizing the GUI
10611090
# widget (both are likewise done in savefig()).
10621091
with (writer.saving(self._fig, filename, dpi),
10631092
cbook._setattr_cm(self._fig.canvas, _is_saving=True, manager=None)):
1093+
if not writer._supports_transparency():
1094+
facecolor = savefig_kwargs.get('facecolor',
1095+
mpl.rcParams['savefig.facecolor'])
1096+
if facecolor == 'auto':
1097+
facecolor = self._fig.get_facecolor()
1098+
savefig_kwargs['facecolor'] = _pre_composite_to_white(facecolor)
1099+
savefig_kwargs['transparent'] = False # just to be safe!
1100+
10641101
for anim in all_anim:
10651102
anim._init_draw() # Clear the initial frame
10661103
frame_number = 0

0 commit comments

Comments
 (0)
0