Goで画像からアニメGifを生成する

# 初めに
この記事は[Go Advent Calendar 2017](https://qiita.com/advent-calendar/2017/go)の13日目の記事です。

SlackのemojiをGolangであれこれしていた時に、このような記事を見かけました。
[Slackで寿司を回転させる技術](http://kurochan-note.hatenablog.jp/entry/2017/09/18/183552)
gifアニメの仕組みがちょっとだけ分かってきた時期だったので、ひょっとしてこれはGolangでgifアニメ化からアップロードまで一気通貫で出来るのではないか…？と思いながらも中々手をつけずにいました。
今回よい機会だったので、gifアニメを作るツールを作ってみました。

以下サンプルです。

![gopher_animated.gif](https://qiita-image-store.s3.amazonaws.com/0/139405/898fcac0-37da-642c-a316-b73edebb040e.gif)

![gopher_bottom_animated.gif](https://qiita-image-store.s3.amazonaws.com/0/139405/5d8377d9-1a41-8238-12f3-46fbb3141f33.gif)

サイズが大きくてすみません！

## 概要
ソースは以下レポジトリとなります。
[gif_anime_creator_go](https://github.com/from-unknown/gif_anime_creator_go)

下記フォーマットの画像をアニメーションGifにすることが出来ます。

- jpg
- png
- gif

アニメーションGifと言ってもシンプルなもので、画像が右から左、上から下などに流れる様なものです。
開始方向と終了方向を選べるので、サンプルの様に下から出てきて下に引っ込むといったことも可能です。

## 使用方法
gif_anime_creator_go [-start=position] [-end=position] image_file  

positionにはleft、right、top、bottomが入れられます。
デフォルトではleftとrightで、左から右に流れるように動きます。

組み合わせは自由に出来ますので、好きな様に動かしてください！
配布用の実行ファイルが用意できていないので、後で用意しようと思っています。

## つまずいたポイント
開発中につまずいたポイントを幾つか紹介します。

**1.画像を動かすことがうまく出来ない**
SubImageで画像を切り取った後に移動後のポジションから描画すればよいと思っていたのですが、いざやってみると動きがおかしい。
思ったような動きにならずにハマりました。

**解決策**
難しいことをする必要はありませんでした。
描画する際にポジションを移動した分だけずらしてあげればちゃんと描画してくれました。
ポイントとしてはx軸に-（マイナス）を指定すると左に移動、+（プラス）なら右に移動します。
y軸の場合は-は上、+は下です。

```go
// Pointは(x,y)で表現されるので、ここをずらすだけでよい
draw.Draw(Palette, Rectangle, Image, Point, draw.Src)
```


**2.背景色が透明にならない**
これは中々悩みました…。
原因は**Paletteの中に透明色がないから**という事は早い段階で分かったのですが、自然に見えるように減色しつつ透明色を追加する方法が分からず、かなり悩みました…。

**解決策**
こちらを使わせて頂きました。
[soniakeys/quant](https://github.com/soniakeys/quant)
指定した数の色に減色してくれるので、255色に減色してから透明色を追加することで色の問題は解決しました。
上記ライブラリはこちらの記事を参考にさせて頂きました。
[Go言語で画像の減色を行う](https://shogo82148.github.io/blog/2015/04/25/quantize-image-in-golang/)
（gifアニメは作ったのか気になります。）


**3.残像が残る**
gifアニメが出来たのはいいのですが、残像が残ってしまい、まるで瞬獄殺の様になってしまいました。
gifは差分のみ描画されるということは以前の経験でわかっていたのですが、これをどうしたらよいのかは当初分かりませんでした。

**解決策**
gifアニメは毎フレームの描画のモードがいくつかあって、モードによっては差分しか描画されません。
GolangのGifでEncodeAllを実行すると、デフォルトでは最適化（差分のみ）になります。
この辺りがさっぱり分かっておらず、gifアニメの仕様を見てようやく理解できました。
※参考リンク
[GIF Animation and Disposal Methods - GIF Animation Studio](http://www.webreference.com/content/studio/disposal.html)
[Cover Sheet for the GIF89a Specification](https://www.w3.org/Graphics/GIF/spec-gif89a.txt)
GolangのGIFのstructを見ると、Disposalと言うものがあります。
特に何も指定しないと0がセットされると書かれています。

```go
type GIF struct {
        Image     []*image.Paletted // The successive images.
        Delay     []int             // The successive delay times, one per frame, in 100ths of a second.
        LoopCount int               // The loop count.
        // Disposal is the successive disposal methods, one per frame. For
        // backwards compatibility, a nil Disposal is valid to pass to EncodeAll,
        // and implies that each frame's disposal method is 0 (no disposal
        // specified).
        Disposal []byte
```

上記資料を確認してDisposalに2をセットすることにより、`Restore to background color`の動作をするようになったので、無事に残像が残ることはなくなりました。

**4.謎の透明色が画像内に出る**
幾つか画像をgifアニメにした時、画像内に透明色が出てしまっておかしくなることがありました。
`draw.FloydSteinberg.Draw`を使って描画していたので、どうしたらよいのか分からず、解決できるか悩みました。

**解決策**
`draw.FloydSteinberg.Draw`を止めて、`draw.Draw`を使う様にしたら発生しなくなりました。
ただし、FloydSteinbergの方が画質は良かったため、若干完成時の見た目は悪くなりました。
これに関しては今はよい方法が分からないため、一旦このままです…。

## 残課題
元々Slackでループさせる様にしたかったのですが、今のままだと並べてもスムーズに繋がりません。
この点に関しては今後機能追加したいと考えています。
また、Slackにアップロードするツールとの連携も出来ていないので、こちらも作りたいと思っています。

## 感想
最初はそこそこ理解しているつもりだったのでそこまでかからないと思っていましたが、やってみるとあちこちでつまずいて想定以上に時間がかかりました。
やっぱりやってみることで学ぶことは多いなーと感じました。

# 参考リンク
本文に出た以外に、以下リンクを参考にさせて頂きました。
ありがとうございました。
[Go 言語でアニメーション GIF を作成する](http://tech.nitoyon.com/ja/blog/2016/01/07/go-animated-gif-gen/)

今後も何かしら作っていきたいと思いますm(_ _)m