使用Go產生動態gif

Reading time ~2 minutes

最近在嘗試寫一些動態gif的應用, 所以就研究了一下怎用go去產生動態gif, 後來找到以下這篇:

Go言語で画像の減色を行う

其實這篇已經寫的蠻完整的了, 不過由它是日文(有很多日文的技術文章其實還不錯), 所以我嘗試用我自己的方法用中文再闡釋一遍

要用輸出動態gif其實go就已經支援了, 使用EncodeAll即可:

https://golang.org/pkg/image/gif/#EncodeAll

不過, 仔細看一下GIF這個struct裡的那個Image陣列, 接受的是Paletted, 也就是附有調色盤的圖(色彩較少), 而現在大部分的圖片大多是全彩的, 因此, 需要先把全彩的圖轉成Paletted, 而這部份的作法是利用image.draw裡的Draw把全彩的圖畫到一個新的Paletted去:

palImg := image.NewPaletted(img.Bounds(), palette.WebSafe)
draw.Draw(palImg, img.Bounds(), img, image.ZP, draw.Over)

由於新創一個Paletted圖, 需要給它一個調色盤, 最簡單的方法是給它預設的調色盤, 在image.color.palette裡有兩個(plan9, web safe), 這邊採用了比較通用的Web safe, 最後輸出的結果如下:

呃, 好醜…..不過這是一定的, web safe一是組比較通用標準的色盤, 但大多數的顏色跟原本的圖的色彩差異可能相當的大, draw.Draw是在調色盤找出相近色, 但有可能找出來這顏色差異就很大了

那怎麼辦?我們就必須對全彩影像找出最佳的調色盤, 從成千上萬色中找出適合的256種顏色, 這在影像處理技巧中有個叫Color quantization, 它就是用來處理這樣的狀況的

在image.draw裡也找到了一個Quantizer:

https://golang.org/src/image/draw/draw.go?s=688:910#L17

不過看來這個Quantizer只有空殼(interface)並未有實作(也就是這樣我才去找到那篇日文的參考文章), 雖說, 實作一個median cut演算法不難, 但對懶人來說, 能有現成的當然最好, 從那篇參考文章中看到了, 有人幾經寫好的(更不錯的是已經實作了Quantizer) -

https://github.com/soniakeys/quant/

使用它後, 先前得程式碼可以簡單的改成 -

q := median.Quantizer(256) //256個顏色
palImg := q.Image(img)

得到的結果會優很多:

這樣好很多了吧?不過還不是最佳的, 由於顏色數變少了, 有些漸層的地方(比如說範例裡企鵝的脖子)會變成一條條帶狀的, 所以我們還需要做dithering, 在Go的image.draw裡其實已經有實作了Floyd Steinberg演算法, 因此可以用這個來做dithering, 程式就改成:

palette := q.Quantize(make(color.Palette, 0, 256), img)
palImg := image.NewPaletted(img.Bounds(), palette)
draw.FloydSteinberg.Draw(palImg, img.Bounds(), img, image.ZP)

出來的結果是這樣:

細部的差異差不多像這樣:

第二張是有dithering的(怎感覺比較醜)

總之, Happy GIFing~~~