B站表情生成 - Go语言与Gif透明色处理
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语言中,预设了两种色版:Plan9
和 WebSafe
,一般采用 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)
}
参考文献

Hello! I am DXkite