おまえもLGTM GIF画像を生成して、チームの生産性を上げてみないか？

# TL;DR

golangで
1. 既存gif拾ってきて
2. 文字列を合成して
3. LGTM画像を大量製造してみた

ソースコード: https://gist.github.com/tng527/484d77db0b86ff6c7aa13435073e1100

# 取り組みのモチベーション

レビュー時に拾い物のLGTM画像を貼っていたところ、以下のように喜んでもらえた。
![スクリーンショット 2020-12-22 13.44.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68104/a6280042-75bf-7b04-3378-24c8acc1ed27.png)

常に新鮮なLGTM画像を利用することで、メンバーのモチベが爆上がりするのではないか？(仮説)

拾い物のLGTM画像は面白みに欠ける画像が多い(個人の感想)ので、生成してみることにした。

# 注意点

いつもどおり、アドカレに間に合わせるための動けば良いやコードです。
エラー処理は基本してませんし、使い勝手の向上は今後必要に応じて対応します。

著作権違反になる可能性があるため、合成前の画像の著作権はよく確認しましょう。(本記事内に画像を添付するのもやめておきます)

# 大まかな処理の流れ

1. キーワードを用いてgifを検索する
2. gifをダウンロードする
3. gifにLGTM文字列を合成する

# 1. キーワードを用いてgifを検索する

### 画像ソース選定

いくつかのGIF画像を提供しているサービスを比較し、APIを提供していて動きがコミカルなGIFを多く提供しているGIPHYを選定。

以下のサイトを参考にした。

GIPHY APIの公式ドキュメント: https://developers.giphy.com/docs/api/endpoint#trending

GIPHY APIの使い方の日本語記事: https://qiita.com/onetk/items/5244a359958bb22f7bb6

### 画像リストの取得コード

ざっとこんな感じ。offsetを指定したければすればいいんじゃない...
環境変数に`GIPHY_KEY`をkeyにしてGIPHYから発行されたAPI Keyをセットしてね。

```go
type ImageInfo struct {
	Url   string
	Title string
}

const getLimit = 50

func getGifUrl(keyword string) []ImageInfo {
	url := "https://api.giphy.com/v1/gifs/search"
	req, _ := http.NewRequest("GET", url, nil)

	params := req.URL.Query()
	params.Add("api_key", os.Getenv("GIPHY_KEY"))
	params.Add("q", keyword)
	params.Add("limit", fmt.Sprint(getLimit))

	req.URL.RawQuery = params.Encode()

	client := new(http.Client)
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	body, _ := ioutil.ReadAll(resp.Body)
	var j interface{}
	json.Unmarshal(body, &j)

	var imgs []ImageInfo
	for i := 0; i < getLimit; i++ { // TODO: getLimitではなく取れた値の数で回す
		url, _ := jsonpointer.Get(j, "/data/"+fmt.Sprint(i)+"/images/original/url")
		title, _ := jsonpointer.Get(j, "/data/"+fmt.Sprint(i)+"/title")
		img := ImageInfo{Url: url.(string), Title: title.(string)}
		imgs = append(imgs, img)
	}

	return imgs
}
```

# 2. gifをダウンロードする

こちらの実装をほぼパクらせていただいた。感謝感謝。
https://qiita.com/yyoshiki41/items/37010dd281bfd29731cc

```go
func download(info ImageInfo) error {
	resp, err := http.Get(info.Url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	file, err := os.Create(path.Join("raw", info.Title+".gif"))
	if err != nil {
		return err
	}

	_, err = io.Copy(file, resp.Body)
	if closeErr := file.Close(); err == nil {
		err = closeErr
	}
	return err
}
```

# 3. gifにLGTM文字列を合成する

こちらをほぼ踏襲。(パクってばっかだな...)
https://qiita.com/syamaoka/items/a85e360970a8111a1dac

こちらは少しだけ工夫していて、文字サイズを元画像の大きさに合わせて自動的に(適当に)調整している。
センタリング等のオプションが見つからなかったので、位置合わせに若干苦労した。
フォントサイズはポイントで指定しなければならないが、位置合わせはピクセルで記載しなければならない(xtぽい)のが肝。
ポイントに1.333を掛ければピクセルになるらしいので、それを採用したところ、だいたいうまく動いてるっぽい。

```go
func combineLGTM(filename string) {
	f, err := os.Open(path.Join("raw", filename))
	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)
	}

	file, err := os.Create(path.Join("lgtm", filename))
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	defer file.Close()

	file.Write(buf.Bytes())
}

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
	}

	log.Println(newImg.Rect.Dx())

	// pixelをpointに直し、4文字分の場所を確保し、左右に若干余裕(0.8倍)をもたせる
	fontsize := float64(newImg.Rect.Dx()) * 0.25 * 0.8 / 1.333

	d := &font.Drawer{
		Dst: newImg,
		Src: image.NewUniform(color.White),
		Face: truetype.NewFace(tt, &truetype.Options{
			Size: fontsize,
		},
		),
		// 横方向はfontsizeを2文字分をpixelに直した値
		Dot: fixed.Point26_6{fixed.Int26_6(((float64(newImg.Rect.Dx()) / 2) - fontsize*2/1.333) * 64), fixed.Int26_6((newImg.Rect.Dy() - 20) * 64)},
	}
	d.DrawString("LGTM")
	return newImg, nil
}
```

# 今後の展望

- 背景色によっては「LGTM」の白文字が見づらいので縁取りしたい
- 元画像の動きによって、「LGTM」の文字の縁が変に黒くなったりしているのでなんとかしたい...
- offsetを定義してループ回したい(現状offset:0の50枚しか取得できない)

# 今回の後日談、というかオチ

作ったあとに気がついたが、既にツールがあった...
https://m0t0k1ch1st0ry.com/blog/2015/03/14/lgtmize/