drawsvg icon indicating copy to clipboard operation
drawsvg copied to clipboard

Transparent gifs

Open AKuederle opened this issue 5 years ago • 3 comments

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)

AKuederle avatar May 30 '20 14:05 AKuederle

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.

cduck avatar May 30 '20 19:05 cduck

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.

AKuederle avatar Jul 03 '20 15:07 AKuederle

No problem. Your code example should be a good starting point if anyone else wants to contribute.

cduck avatar Jul 05 '20 00:07 cduck