5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

フューチャーAdvent Calendar 2020

Day 23

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

Posted at

TL;DR

golangで

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

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

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

レビュー時に拾い物のLGTM画像を貼っていたところ、以下のように喜んでもらえた。
スクリーンショット 2020-12-22 13.44.27.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をセットしてね。

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

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を掛ければピクセルになるらしいので、それを採用したところ、だいたいうまく動いてるっぽい。

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/

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?