@@ -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,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' , '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