Would you like to generate LGTM GIF images to increase your team's productivity?

TL;DR

with golang

  1. Pick up an existing gif
  2. Synthesize the character string
  3. I tried mass-producing LGTM images

Source code: https://gist.github.com/tng527/484d77db0b86ff6c7aa13435073e1100

Motivation for efforts

When I posted the LGTM image of the picked up item at the time of the review, I was pleased as follows. スクリーンショット 2020-12-22 13.44.27.png

Isn't the motivation of the members exploding by always using fresh LGTM images? (hypothesis)

Many of the LGTM images I picked up are uninteresting (personal impressions), so I decided to generate them.

important point

As usual, it's just a code to move in time for ad-care. Error handling is not basic, and usability improvements will be made as needed in the future.

Be sure to check the copyright of the image before compositing, as it may violate the copyright. (I will not attach images in this article)

Rough processing flow

  1. Search for gifs using keywords
  2. Download gif
  3. Combine LGTM string with gif

1. Search for gifs using keywords

Image source selection

We compared services that provide several GIF images and selected GIPHY, which provides APIs and provides many GIFs with comical movements.

I referred to the following site.

Official GIPHY API documentation: https://developers.giphy.com/docs/api/endpoint#trending

Japanese article on how to use the GIPHY API: https://qiita.com/onetk/items/5244a359958bb22f7bb6

Image list acquisition code

Roughly like this. You don't have to specify offset ... Set the environment variable to the API Key issued by GIPHY with GIPHY_KEY as the 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:Turn by the number of values ​​taken instead of 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. Download gif

I've almost done this implementation. Thank you very much. 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. Combine LGTM string with gif

Almost follow this. (Paku is just ...) https://qiita.com/syamaoka/items/a85e360970a8111a1dac

This is a little tricky, and the font size is automatically (appropriately) adjusted to match the size of the original image. I couldn't find any options such as centering, so I had some difficulty in aligning. The font size must be specified in points, but the alignment must be described in pixels (xt-like). It seems that if you multiply the point by 1.333, it will be a pixel, so when I adopted it, it seems that it works almost well.

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) {
	//Generate img to fit gif size
	newImg := image.NewRGBA(img.Rect)
	tt, err := truetype.Parse(gobold.TTF)
	if err != nil {
		return nil, err
	}

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

	//Correct pixel to point, secure a place for 4 characters, and have some margin on the left and right(0.8 times)To have
	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,
		},
		),
		//In the horizontal direction, the font size is converted to pixels for 2 characters.
		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
}

Future outlook

--Depending on the background color, the white characters of "LGTM" are difficult to see, so I want to border them. ――Due to the movement of the original image, the edges of the letters "LGTM" are strangely blackened, so I want to do something about it ... --I want to define offset and loop around (currently, I can only get 50 offset: 0)

This later talk, or rather punch line

I noticed after making it, but I already had a tool ... https://m0t0k1ch1st0ry.com/blog/2015/03/14/lgtmize/

Recommended Posts

Would you like to generate LGTM GIF images to increase your team's productivity?
Would you like to analyze your escape shame yourself?
[Recruitment] Would you like to exchange programming skills?
Nice to meet you, I would like to introduce myself.