TL;DR
golangで
- 既存gif拾ってきて
- 文字列を合成して
- LGTM画像を大量製造してみた
ソースコード: https://gist.github.com/tng527/484d77db0b86ff6c7aa13435073e1100
取り組みのモチベーション
レビュー時に拾い物のLGTM画像を貼っていたところ、以下のように喜んでもらえた。
常に新鮮なLGTM画像を利用することで、メンバーのモチベが爆上がりするのではないか?(仮説)
拾い物のLGTM画像は面白みに欠ける画像が多い(個人の感想)ので、生成してみることにした。
注意点
いつもどおり、アドカレに間に合わせるための動けば良いやコードです。
エラー処理は基本してませんし、使い勝手の向上は今後必要に応じて対応します。
著作権違反になる可能性があるため、合成前の画像の著作権はよく確認しましょう。(本記事内に画像を添付するのもやめておきます)
大まかな処理の流れ
- キーワードを用いてgifを検索する
- gifをダウンロードする
- 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/