🐕

Go 画像の読み込み、リサイズについて

2022/01/23に公開

はじめに

実務にて画像をリサイズする機能を実装する機会があったため今回サンプルとして取り組みました。

使用したパッケージ

  • github.com/nfnt/resize
  • golang.org/x/image/draw

前者の方は四年前に更新が止まっており、アナウンスがあるように他のパッケージを使用することが推奨されている。そのため、今回は比較的stars数、開発頻度含めて後者のパッケージを使用してみました。

サンプルコード

main.go
package main

import (
	"fmt"
	"image"
	"image/jpeg"
	"log"
	"os"

	// "github.com/nfnt/resize"
	"golang.org/x/image/draw"
)

/*
 * 必要に応じてパッケージのコメントアウトを解除
 */

// func Resize() {
// 	img, err := os.Open("./tmp/sample.jpg")
// 	if err != nil {
// 		log.Fatal(err)
// 	}
// 	defer img.Close()

// 	decordImg, err := jpeg.Decode(img)
// 	if err != nil {
// 		log.Fatal(err)
// 	}
// 	bound := decordImg.Bounds()
// 	fmt.Printf("sample.jpg size \nX: %dpx, Y: %dpx\n", bound.Dx(), bound.Dy())

// 	m := resize.Resize(uint(bound.Dx()/4), uint(bound.Dy()/4), decordImg, resize.Lanczos3)

// 	dst, err := os.Create("./img/resize_sample.jpg")
// 	if err != nil {
// 		log.Fatal(err)
// 	}
// 	defer dst.Close()

// 	jpeg.Encode(dst, m, nil)
// }

func drawSample() {
	src, err := os.Open("./tmp/sample.jpg")
	if err != nil {
		log.Fatal(err)
	}
	defer src.Close()

	imgSrc, t, err := image.Decode(src)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}
	fmt.Println("Type of image: ", t)

	rctSrc := imgSrc.Bounds()

	imgDst := image.NewRGBA(image.Rect(0, 0, rctSrc.Dx()/4, rctSrc.Dy()/4)) // 669KB -> 142KB
	draw.CatmullRom.Scale(imgDst, imgDst.Bounds(), imgSrc, rctSrc, draw.Over, nil)

	dst, err := os.Create("./img/resize_sample.jpg")
	if err != nil {
		log.Fatal(err)
	}
	defer dst.Close()

	jpeg.Encode(dst, imgDst, &jpeg.Options{Quality: 100})
}

func main() {
	// Resize()
	drawSample()
}

二つをまとめたので、上記一覧はコメントアウトしている部分があります。

コードの簡単な解説

github.com/nfnt/resize の使用箇所について

func Resize() {
	img, err := os.Open("./tmp/sample.jpg")
	if err != nil {
		log.Fatal(err)
	}
	defer img.Close()

	decordImg, err := jpeg.Decode(img)
	if err != nil {
		log.Fatal(err)
	}
	bound := decordImg.Bounds()
	fmt.Printf("sample.jpg size \nX: %dpx, Y: %dpx\n", bound.Dx(), bound.Dy())

	m := resize.Resize(uint(bound.Dx()/4), uint(bound.Dy()/4), decordImg, resize.Lanczos3)

	dst, err := os.Create("./img/resize_sample.jpg")
	if err != nil {
		log.Fatal(err)
	}
	defer dst.Close()

	jpeg.Encode(dst, m, nil)
}

こちらはかなりシンプルで、取得した画像をデコードし、

bound := decordImg.Bounds()

この箇所でデコードしたものの、範囲領域(画像のサイズなどの情報)を取得

m := resize.Resize(uint(bound.Dx()/4), uint(bound.Dy()/4), decordImg, resize.Lanczos3)

そしてresizeパッケージのResize()で画像の縮小、拡大を行います。
引数に、
1: 横のサイズの指定
2: 縦のサイズの指定
3: 変更する画像
4: ランツォシュ補間
となります。
聞き慣れない第4引数の「ランツォシュ補間」ですが、操作した画像を出力した際の品質レベルになると思います。ここではサンプルなので上記のLanczos3を使用してしますが処理は重いようです。(比べてもないので細かくは調べていません)
後の保存はGoの書き方の通りになりますね。

golang.org/x/image/draw の使用箇所について

src, err := os.Open("./tmp/sample.jpg")
	if err != nil {
		log.Fatal(err)
	}
	defer src.Close()

	imgSrc, t, err := image.Decode(src)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}
	fmt.Println("Type of image: ", t)

	rctSrc := imgSrc.Bounds()

	imgDst := image.NewRGBA(image.Rect(0, 0, rctSrc.Dx()/4, rctSrc.Dy()/4)) // 669KB -> 142KB
	draw.CatmullRom.Scale(imgDst, imgDst.Bounds(), imgSrc, rctSrc, draw.Over, nil)

	dst, err := os.Create("./img/resize_sample.jpg")
	if err != nil {
		log.Fatal(err)
	}
	defer dst.Close()

	jpeg.Encode(dst, imgDst, &jpeg.Options{Quality: 100})

全体の流れとしてはほぼ同じです。
変更部分は

imgDst := image.NewRGBA(image.Rect(0, 0, rctSrc.Dx()/4, rctSrc.Dy()/4))
draw.CatmullRom.Scale(imgDst, imgDst.Bounds(), imgSrc, rctSrc, draw.Over, nil)

NewRGBA関数で表示領域の縮小を行います。
draw.CatmullRom.Scale()で
縮小した表示領域に対して、出力しデコードしたデータを流し込みリサイズした画像を作り上げていくイメージなのかなと思います。

まとめ

今回は二つのパッケージを使用しリサイズを行いました。サンプルなのでjpegの画像しか使用していませんが、読み込む画像によってはgif,pngなど色々あると思うのでその都度画像種類の取得、判別、処理の分岐が必要になってくるのかと思います。

次回はフロントを含めた画像のアップロードについてまとめようかなと思います。

Discussion