drawsvg
drawsvg copied to clipboard
Transparent gifs
First of all thank you for this nice package! I was trying to export an animation as a gif with a transparent background. Unfortunately, this was not possible using imageio (at least based on my googeling). There is a solution using PIL (see below).
However, this requires implementing a custom save function. Would you be interested in adding alternative support for PIL to save gifs to allow this solution natively? I would be willing to implement this properly. If not, I hope this issue serves as potential information for others who ran into the same issue I did.
import drawSvg as draw
from PIL import Image
from drawSvg import Drawing, render_svg_frames
def convert_to_transparent_frame(im):
"""Modified from https://stackoverflow.com/questions/46850318/transparent-background-in-gif-using-python-imageio"""
alpha = im.getchannel("A")
# Convert the image into P mode but only use 255 colors in the palette out of 256
im = im.convert("RGB").convert("P", palette=Image.ADAPTIVE, colors=255)
# Mark all pixels with alpha=0 as transparent
mask = Image.eval(alpha, lambda a: 255 if a == 0 else 0)
# Paste the color of index 255 and use alpha as a mask
im.paste(255, mask)
# The transparency index is 255
im.info["transparency"] = 255
return im
def save_video(frames, file, **kwargs):
"""
Save a series of drawings as a GIF or video.
Arguments:
frames: A list of `Drawing`s or a list of `numpy.array`s.
file: File name or file like object to write the video to. The
extension determines the output format.
align_bottom: If frames are different sizes, align the bottoms of each
frame in the video.
align_right: If frames are different sizes, align the right edge of each
frame in the video.
bg: If frames are different sizes, fill the background with this color.
(default is white: (255, 255, 255, 255))
duration: If writing a GIF, sets the duration of each frame.
fps: If writing a video, sets the frame rate in FPS.
**kwargs: Other arguments to imageio.mimsave().
"""
if isinstance(frames[0], Drawing):
frames = render_svg_frames(frames, **kwargs)
kwargs.pop("align_bottom", None)
kwargs.pop("align_right", None)
kwargs.pop("bg", None)
frames = [
convert_to_transparent_frame(Image.fromarray(frame, "RGBA")) for frame in frames
]
frames[0].save(
file,
save_all=True,
append_images=frames[1:],
disposal=2, # This "cleans" the canvas after every draw
optimize=False, # This is critical for transparent background
**kwargs,
)
# Draw a frame of the animation
def draw_frame(t):
d = draw.Drawing(2, 6.05, origin=(-1, -1.05))
d.setRenderSize(h=300)
# d.append(draw.Rectangle(-2, -2, 4, 8, fill="white")) # Commented out, as we want to have transparent background
d.append(draw.Rectangle(-1, -1.05, 2, 0.05, fill="brown"))
t = (t + 1) % 2 - 1
y = 4 - t ** 2 * 4
d.append(draw.Circle(0, y, 1, fill="lime"))
return d
with draw.animate_video(None, draw_frame) as anim:
# Add each frame to the animation
for i in range(20):
anim.draw_frame(i / 10)
for i in range(20):
anim.draw_frame(i / 10)
for i in range(20):
anim.draw_frame(i / 10)
save_video(anim.frames, "test.gif", duration=0.05)
This looks great. I think it would be a nice addition to drawSvg.
My suggestion would be to make code like this work:
with draw.animate_video('example6.gif', draw_frame, duration=0.05,
gif_transparency=True, gif_alpha_threshold=128
) as anim:
# Add each frame to the animation
...
Or maybe a separate function like with draw.animate_gif('test.gif', duration=0.05, alpha_threshold=0) as anim:.
Let me know if you have questions implementing this or making a pull request.
Sry for the silence. Unfortunately, live came in the way of the project I was using this for. At the moment, I don't know when I will have time to look into this again.
No problem. Your code example should be a good starting point if anyone else wants to contribute.