Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 3fa8b10

Browse filesBrowse files
committed
anim: Only pre-composite formats that are opaque
On video formats like `.mp4`, it makes sense to do our own pre-compositing to white. The animation backends would otherwise do it themselves, and tend to just make semi-transparent pixels either fully transparent or fully opaque before placing on white. So doing it ourselves corrects that error. However, formts like `.gif`, or `.webm` _do_ support transparency, and we shouldn't do the compositing then. Fixes #27173
1 parent 102fac3 commit 3fa8b10
Copy full SHA for 3fa8b10

File tree

Expand file treeCollapse file tree

1 file changed

+44
-8
lines changed
Filter options
Expand file treeCollapse file tree

1 file changed

+44
-8
lines changed

‎lib/matplotlib/animation.py

Copy file name to clipboardExpand all lines: lib/matplotlib/animation.py
+44-8Lines changed: 44 additions & 8 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,6 +514,20 @@ 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', '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 = []
@@ -519,11 +544,17 @@ def output_args(self):
519544
# macOS). Also fixes internet explorer. This is as of 2015/10/29.
520545
if self.codec == 'h264' and '-pix_fmt' not in extra_args:
521546
args.extend(['-pix_fmt', 'yuv420p'])
522-
# 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
523548
# a palette, and then use it for encoding.
524549
elif self.codec == 'gif' and '-filter_complex' not in extra_args:
525550
args.extend(['-filter_complex',
526551
'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]'])
527558
if self.bitrate > 0:
528559
args.extend(['-b', '%dk' % self.bitrate]) # %dk: bitrate in kbps.
529560
for k, v in self.metadata.items():
@@ -611,6 +642,10 @@ class ImageMagickBase:
611642
_exec_key = 'animation.convert_path'
612643
_args_key = 'animation.convert_args'
613644

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

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

1058-
savefig_kwargs['facecolor'] = _pre_composite_to_white(facecolor)
1059-
savefig_kwargs['transparent'] = False # just to be safe!
10601088
# canvas._is_saving = True makes the draw_event animation-starting
10611089
# callback a no-op; canvas.manager = None prevents resizing the GUI
10621090
# widget (both are likewise done in savefig()).
10631091
with (writer.saving(self._fig, filename, dpi),
10641092
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+
10651101
for anim in all_anim:
10661102
anim._init_draw() # Clear the initial frame
10671103
frame_number = 0

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.