Gif是没有透明色的,所以在处理透明色的时候,需要指定一种颜色作为透明色来实现透明。

因为朋友一个小小的想法,想把B站的小电视表情包处理成gif,为了方便使用,我采用了go语言作为处理工具(为什么不是python? 大概因为我现在觉得go写脚本很舒服),用Go编写之后,基本实现了一次编写多处运行,而不是JAVA的一次编写到处报错(大雾),以下为待处理的图片和处理之后的图片:

处理前:

处理后:

因为图片色彩差异,生成的gif的色彩稍许有点失真,毕竟PNG的色彩原图是24位的,变成8位的色彩,不失真才怪,不过后续可以考虑使用 APNG 处理成动态的PNG来实现(貌似golang官方暂时没有?大概),源码可以使用 go get dxkite.cn/demo/GoGif 命令获取,也可以查看Github 源码。

Gif 的色板

在Gif中,色彩的数量最多为8位色彩,也就是256种颜色,在Go语言中,预设了两种色版:Plan9WebSafe,一般采用 Plan9 作为基本色版,从Gif的结构来看,色版的大小也影响着Gif生成之后的大小,所以色版越小越好,但是在我们使用Go将PNG转换成Gif的时候,默认使用的是Plan9的色版。但是Plan9的色版是没有透明色的,当我们使用Go的标准Gif库将PNG转换成GIF的时候会发现透明色的部分变成了黑色。

在Go语言中,有特定的方式实现透明色 image/gif/writer.go#L261-L271, 即从色板中找出alpha通道为0的颜色作为透明处理(Plan9没有)

    transparentIndex := -1
    for i, c := range pm.Palette {
        if c == nil {
            e.err = errors.New("gif: cannot encode color table with nil entries")
            return
        }
        if _, _, _, a := c.RGBA(); a == 0 {
            transparentIndex = i
            break
        }
    }

所以我们要实现PNG转换成Gif的同时还要自行构建色版,按理说色版构建这个功能,标准库应该给我们构建好了,但是事实上却是这样的image/gif/writer.go#L447-L457, 对,就是没空,啊哈哈哈

    if pm == nil || len(pm.Palette) > opts.NumColors {
        // Set pm to be a palettedized copy of m, including its bounds, which
        // might not start at (0, 0).
        //
        // TODO: Pick a better sub-sample of the Plan 9 palette.
        pm = image.NewPaletted(b, palette.Plan9[:opts.NumColors])
        if opts.Quantizer != nil {
            pm.Palette = opts.Quantizer.Quantize(make(color.Palette, 0, opts.NumColors), m)
        }
        opts.Drawer.Draw(pm, b, m, b.Min)
    }

所以,我们需要自己实现色版处理,具体逻辑:从图片中找出所有颜色,并且用Plan9标准化,如果颜色数量不能够添加透明色则不处理,如果可以,则插入透明色,否则使用黑色作为透明色,代码:GoGif/gogif/base.go#L9-L41

func inPalette(p color.Palette, c color.Color) int {
    ret := -1
    for i, v := range p {
        if v == c {
            return i
        }
    }
    return ret
}

func getSubPalette(m image.Image) color.Palette {
    p := color.Palette{color.RGBA{0x00,0x00,0x00,0x00}}
    p9 := color.Palette(palette.Plan9)
    b := m.Bounds()
    black := false
    for y := b.Min.Y; y < b.Max.Y; y++ {
        for x := b.Min.X; x < b.Max.X; x++ {
            c := m.At(x, y)
            cc := p9.Convert(c)
            if cc == p9[0] {
                black = true
            }
            if inPalette(p, cc) == -1 {
                p = append(p, cc)
            }
        }
    }
    if len(p) < 256 && black == true {
        p[0] = color.RGBA{0x00,0x00,0x00,0x00} // transparent
        p = append(p, p9[0])
    }
    return p
}

处理完之后,可以得到一个Plan9色版的子集(含透明色)

本来想提交到go的源码来着,不知道怎么提交,反正怎么看怎么麻烦

从PNG生成GIF

从PNG生成GIF的方式也比较简单,控制一个滑动窗口来将一长条的图片,裁剪绘制到Gif上 GoGif/gogif/make.go#L36-L44

    var images []*image.Paletted
    var delays []int
    cp :=  getSubPalette(img)
    for y := 0; y < img.Bounds().Max.Y; y += rect.Dy() {
        pm := image.NewPaletted(rect, cp)
        draw.Draw(pm, rect, img, image.Pt(0, y), draw.Src)
        images = append(images, pm)
        delays = append(delays, delay)
    }

参考文献