英文:
How to split gif into images
问题
你可以使用Go语言将GIF分割成图像。image/gif包中的DecodeAll函数返回一个GIF对象,其中包含一个调色板数组。但是,我不知道如何将每个调色板转换为图像。
英文:
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?
答案1
得分: 8
考虑以下内容:
帧可以包含透明像素或区域,一个很好的例子是维基百科上的这个图片(https://de.wikipedia.org/wiki/Graphics_Interchange_Format#/media/File:SmallFullColourGIF.gif),每帧(我猜)都有一个这样的全彩块,其余部分是透明的。
这给你带来了一个问题:特别是对于不使用多个帧创建真彩静态图像的动画GIF,DecodeAll
返回的帧与你在浏览器中打开图像时实际看到的内容是不同的。
你需要以与浏览器相同的方式处理图像,即在一种画布上保留旧帧并用新帧进行覆盖。但是这并不总是正确的。据我所知,GIF帧可以包含一个处理方法,指定如何(或是否)应该处理该帧。
无论如何,为了达到你的目的,在大多数情况下最简单的方法是像下面这样:
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("解码时出错:%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)
// 保存当前帧“堆栈”。这将覆盖同名的现有文件
file, err := os.Create(fmt.Sprintf("<某个路径>%d.png", i))
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
}
请注意,gif.DecodeAll
经常会引发恐慌,因为互联网上的许多GIF图像都有些问题。你的浏览器会尝试解码它们,并且会用黑色替换丢失的颜色。image/gif
不会这样做,而是引发恐慌。这就是为什么我们使用defer
和recover
的原因。
另外,我使用getGifDimensions
是出于与上述相似的原因:单个帧可能不是你在浏览器中看到的内容。在这种情况下,帧只是完整图像的一部分小得多,这就是为什么我们必须遍历所有帧并获取图像的“真实”尺寸的原因。
如果你真的非常想做得正确,你应该阅读GIF规范GIF87a、GIF89a以及类似这篇文章的内容,后者更容易理解。从中,你应该决定如何处理帧并在覆盖时处理透明度。
编辑:你可以很容易地观察到前面提到的一些效果,如果你拆分一些在线的GIF,例如这个或这个 - 尝试调整“忽略优化”和“使用前一帧的细节重新绘制每一帧”来看看我是什么意思。
英文:
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.
答案2
得分: 1
image.Image
是一个接口,*image.Paletted
实现了这个接口,所以例如如果你想将 GIF 的每一帧保存为 PNG 文件,你可以对每个图像进行编码:
for i, frame := range img.Image {
frameFile, err := os.OpenFile(fmt.Sprintf("%d.png", i+1), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
if err != nil {
log.Fatal(err)
}
err = png.Encode(frameFile, frame)
if err != nil {
log.Fatal(err)
}
// 在这里不使用 defer,因为我们在一个循环中,而不是一个函数中。
frameFile.Close()
}
英文:
image.Image
is an interface, and *image.Paletted
implements the interface, so for example if you want to save every frame of a GIF into a PNG file, you can just encode every image:
for i, frame := range img.Image {
frameFile, err := os.OpenFile(fmt.Sprintf("%d.png", i+1), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
if err != nil {
log.Fatal(err)
}
err = png.Encode(frameFile, frame)
if err != nil {
log.Fatal(err)
}
// Not using defer here because we're in a loop, not a function.
frameFile.Close()
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论