Go を用いて猫 GIF に LGTM テキストをつけたかった

## 最初に
![35f52309-0054-4794-880b-52e0be6612c0.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30359/cd6dc344-968c-599c-ed7c-8620576ded02.gif)

上記が `font.Drawer` を用いて **GIF に対しLGTMを付与**した例です :sob: 
まぁこれはこれでアリかなって思っちゃいますが、LGTM が躍動感出て不具合感が否めません。

※ 文字に被らない自覚ある猫の場合は以下の通りです。
![813c4da8-9735-42fe-9142-f9b8cf819c15.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30359/ef872ba4-8843-ddc2-76ed-10f0c823c179.gif)


## 原因
https://golang.org/pkg/image/gif/#GIF

残像が残るのは Gif クラスの Disposal が原因と考えられる。
Go で設定出来る Disposal Method は３種類あり、

```golang
DisposalNone       = 0x01
DisposalBackground = 0x02
DisposalPrevious   = 0x03
```

取得元の GIF にはドキュメント通りデフォルトの `DisposalNone` が設定されていたので、
`DisposalBackground` に変更すれば、残像が残らないのではと考えたが、
`DisposalNone` から `DisposalBackground` に Disposal を指定し直しても描画崩れは発生していた。

※ Disposal の詳細は以下のページに詳しく解説されています。
http://www.snap-tck.com/room03/c02/cg/cg04_02.html

### 残像が残ってしまうソースコード
```golang
func main() {
	f, err := os.Open("hoge.gif")
	if err != nil {
		panic(err)
	}
	defer f.Close()
	
	g, err := gif.DecodeAll(f)
	if err != nil {
		panic(err)
	}

	for _, img := range g.Image {
		if err = addText(img, "LGTM"); err != nil {
			panic(err)
		}
	}

	newFile, err := os.Create("edited_hoge.gif")
	if err != nil {
		return "", err
	}
	defer newFile.Close()

	if err := gif.EncodeAll(newFile, g); err != nil {
		return "", err
	}
}

// addText は img に対して text を中央下部に描画する
func addText(img *image.Paletted, text string) error {
	tt, err := truetype.Parse(gobold.TTF)
	if err != nil {
		return err
	}
	
	d := &font.Drawer{
		Dst:  img,
		Src:  image.NewUniform(color.White),
		Face: truetype.NewFace(tt, &truetype.Options{
			Size:40.0,
		}),
		Dot:  fixed.Point26_6{
			fixed.Int26_6(((img.Rect.Dx()/2) - 60) * 64),
			fixed.Int26_6((img.Rect.Dy()-20) * 64),
		},
	}
	d.DrawString(text)
	return nil
}
```

## 解決方法
`font.Drawer` を用いるのではなく、 
**LGTM 文字列のみの画像を動的に作成し、GIF に合成** する方法で残像の問題は解決しました。

![tmp.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30359/2d3deaff-7687-180d-4045-f1d615e66fad.gif)


### 残像がないソースコード
```golang
func main() {
	f, err := os.Open("hoge.gif")
	if err != nil {
		panic(err)
	}
	defer f.Close()
	
	g, err := gif.DecodeAll(f)
	if err != nil {
		panic(err)
	}

	lgtmImage, err := generateLGTMImage(g.Image[0]);
	if err != nil {
		panic(err)
	}

	var images []*image.Paletted
	var delays []int
	var disposals []byte

	for i, img := range g.Image {
		logoRectangle := image.Rectangle{image.Point{0, 0}, lgtmImage.Bounds().Size()}
		draw.Draw(img, logoRectangle, lgtmImage, image.Point{0, 0}, draw.Over)
		images = append(images, img)
		delays = append(delays, g.Delay[i])
		disposals = append(disposals, gif.DisposalNone)
	}

	buf := new(bytes.Buffer)
	if err = gif.EncodeAll(buf, &gif.GIF{
		Image:           images,
		Delay:           delays,
		Disposal:        disposals,
		BackgroundIndex: g.BackgroundIndex,
		Config: g.Config,
	}); err != nil {
		panic(err)
	}

	return buf.Bytes(), nil
}

func generateLGTMImage(img *image.Paletted) (image.Image, error) {
	// gif のサイズに合わせて img を生成
	newImg := image.NewRGBA(img.Rect)
	tt, err := truetype.Parse(gobold.TTF)
	if err != nil {
		return nil, err
	}

	d := &font.Drawer{
		Dst:  newImg,
		Src:  image.NewUniform(color.White),
		Face: truetype.NewFace(tt, &truetype.Options{
				Size:40,
			},
		),
		Dot:  fixed.Point26_6{fixed.Int26_6(((newImg.Rect.Dx()/2) - 60) * 64), fixed.Int26_6((newImg.Rect.Dy()-20) * 64)},
	}
	d.DrawString("LGTM")
	return newImg, nil
}
```

## 感想
この問題にハマった時に `font.Drawer` でどうにかならないか試行錯誤してましたが、
別の方法である「合成」で問題解消出来たのでよかったです。

これで猫 gif で心置きなく LGTM 出来ます！
以下のサイトから使ってもいいですし、自分のオリジナルの LGTM gif でも活用してください。

https://lgtm-cat.com/

### 余談
LGTM Cat は Go + Firestore + Cloud Run で作成してます。
元々は Node.JS + GAE (F1 1台)で運用していたのですが、gif を並べて表示しているのでメモリが足りずによくサーバーが落ちてしまっていました。
その点 Cloud Run は無料枠が多い且つ、メモリも好きなだけ増やせれるので上記の問題を解決出来ました。