@@ -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' )
470478class 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