Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to split gif into images

Tags:

go

How can I split gif into images in go? image/gif's DecodeAll return GIF, which contains an array of palette. But don't know how to convert each palette into an image?

like image 559
Bhoomtawath Plinsut Avatar asked Feb 08 '23 17:02

Bhoomtawath Plinsut


1 Answers

Consider the following:
Frames can contain transparent pixels or areas, a good example is this image on wikipedia which (I guess) has one of these full-color blocks per frame and the rest of the frame transparent.

This introduces a problem for you: Especially with animated GIFs, that do not use multiple frames to create a true-colored static image, the frames that DecodeAll returns are not what you actually see if you, for example, open the image in your browser.

You'll have to process the image in the same way your browser would, i.e. leave the old frames on a kind of canvas and overpaint with the new frame. BUT this is not always true. GIF frames can, AFAIK, contain a disposal method, specifying how (or if?) you should dispose of the frame.

Anyways, to get to your point, the most simple approach that will also work in most cases is something like

import (
    "errors"
    "fmt"
    "image"
    "image/draw"
    "image/gif"
    "image/png"
    "io"
    "os"
)

// Decode reads and analyzes the given reader as a GIF image
func SplitAnimatedGIF(reader io.Reader) (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("Error while decoding: %s", r)
        }
    }()

    gif, err := gif.DecodeAll(reader)

    if err != nil {
        return err
    }

    imgWidth, imgHeight := getGifDimensions(gif)

    overpaintImage := image.NewRGBA(image.Rect(0, 0, imgWidth, imgHeight))
    draw.Draw(overpaintImage, overpaintImage.Bounds(), gif.Image[0], image.ZP, draw.Src)

    for i, srcImg := range gif.Image {
        draw.Draw(overpaintImage, overpaintImage.Bounds(), srcImg, image.ZP, draw.Over)

        // save current frame "stack". This will overwrite an existing file with that name
        file, err := os.Create(fmt.Sprintf("%s%d%s", "<some path>", i, ".png"))
        if err != nil {
            return err
        }

        err = png.Encode(file, overpaintImage)
        if err != nil {
            return err
        }

        file.Close()
    }

    return nil
}

func getGifDimensions(gif *gif.GIF) (x, y int) {
    var lowestX int
    var lowestY int
    var highestX int
    var highestY int

    for _, img := range gif.Image {
        if img.Rect.Min.X < lowestX {
            lowestX = img.Rect.Min.X
        }
        if img.Rect.Min.Y < lowestY {
            lowestY = img.Rect.Min.Y
        }
        if img.Rect.Max.X > highestX {
            highestX = img.Rect.Max.X
        }
        if img.Rect.Max.Y > highestY {
            highestY = img.Rect.Max.Y
        }
    }

    return highestX - lowestX, highestY - lowestY
}

(untested, but should work)

Note that gif.DecodeAll can and will panic frequently, because a lot of the GIF images on the internet are somewhat broken. Your browser tries to decode them and will, for example, replace missing colors with black. image/gif will not do that, but panic instead. That's why we defer the recover.

Also, I used the getGifDimensions for a similar reason as stated above: single frames need not be what you see in your browser. In this case, the frames are just smaller than the complete image, that's why we have to iterate over all frames and get the "true" dimensions of the image.

If you really really want to do it right, you should probably read the GIF spec GIF87a, GIF89a and something like this article which is a lot easier to understand. From that, you should decide how to dispose of the frames and what to do with transparency while overpainting.

EDIT: Some of the effects mentioned earlier can be observed easily if you split some GIFs online, for example this or this - play around with "Ignore optimizations" and "Redraw every frame with details from previous frames" to see what I mean.

like image 105
mrd0ll4r Avatar answered Feb 11 '23 21:02

mrd0ll4r