はじめに
画像のリサイズは、業務だとimageFluxを使ったりすることが多いかもしれません。
imageFluxなどを利用することができる環境であれば良いのですが、個人開発や、コストをかけたくないなどの理由で、Goを画像のリサイズを行う方法について書いてみました。
処理の流れ
1.画像データのデコードしてwidthとheightの値を取得する
2.画像のリサイズ処理
実際の処理
1. 画像データのデコードしてwidthとheightの値を取得する
画像データのデコードと、widthとheightの取得をコードで書くと以下のような感じになります。
package main
import (
"fmt"
"image"
_ "image/jpeg"
"os"
)
func main() {
data, err := os.Open("./image.jpg")
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
defer data.Close()
imgData, _, err := image.Decode(data)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
// 画像の矩形情報を取得しそこからwidthとheightの値を取得する.
imgRectangle := imgData.Bounds()
width := imgRectangle.Dx()
height := imgRectangle.Dy()
}
ちなみに、widthとheightを取得するだけなら以下のように、image.DecodeConfigでも取得できます。ImgDecodeConfig関数の引数にはos.Openなどで取得した画像ファイルが入ります。
func ImgDecodeConfig(data *os.File) error {
config, type, err := image.DecodeConfig(data)
if err != nil {
return err
}
fmt.Println(config.Width)
fmt.Println(config.Height)
}
注意点として、 上のコードで、"image/jpeg"
がブランクimportできていない状態で実行すると以下のようなエラーになります。
image: unknown format
理由は、image.Decode関数(image.DecodeConfigも)が実行されるとき、対象の画像のフォーマットが認識できないからです。
imageパッケージの冒頭にも以下のように記載されています。
Decoding any particular image format requires the prior registration of a decoder function
Registration is typically automatic as a side effect of initializing that format's package so that
ブランクインポートすることで、インポートされたフォーマットはデコードする際の対象のフォーマットとして利用できるようです。
具体的には、jpegの場合、imageパッケージのRegisterFormat関数が、image/jpegパッケージのinit関数で呼ばれることで初期化されています。
// https://golang.org/src/image/jpeg/reader.go L816
func init() {
image.RegisterFormat("jpeg", "\xff\xd8", Decode, DecodeConfig)
}
私はこのブランクインポート忘れて、ずっと unknown format
エラーにハマってました。。。
またpngの場合は、 image/png
をブランクインポートする必要があります。
2.画像のリサイズ処理
リサイズ処理のコードはこちらです。記事の最初のほうで共有した画像のデコードとwidthとheightの取得のコードの続きになります。
package main
import (
"fmt"
"image"
"image/jpeg"
_ "image/jpeg"
"math"
"os"
"golang.org/x/image/draw"
)
func main() {
// 最初の実装例コードと同じ
// 以下リサイズ処理部分
// widthかheightの長い方を750に固定してリサイズします。
limitEdge := 750
// 以下の条件分岐で、リサイズしたいサイズで空のインスタンスを作成する。
newImgData := &image.RGBA{}
// heightがwidth以上の場合
if height >= width {
f := float64((width * limitEdge))
w := math.Round(f / float64(height))
newImgData = image.NewRGBA(image.Rect(0, 0, int(w), limitEdge))
} else {
f := float64((limitEdge * height))
h := math.Round(f / float64(width))
newImgData = image.NewRGBA(image.Rect(0, 0, int(h), limitEdge))
}
// drawパッケージを使って、画像データを反映させる。
// CatmullRomを使っているが、drawパッケージに他にも反映させる関数が用意されている。
draw.CatmullRom.Scale(newImgData, newImgData.Bounds(), imgData, imgRectangle, draw.Over, nil)
newImg, err := os.Create("./new_image.jpg")
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
defer newImg.Close()
// jpegの場合のエンコード、各フォーマットごとにエンコードの方法は異なります。
if err := jpeg.Encode(newImg, newImgData, &jpeg.Options{Quality: 100}); err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
}
drawパッケージに定義されているScale関数で画像データを反映させるのですが、drawパッケージに定義されているScalerは4つあります。
drawパッケージScaler定義部分
- NearestNeighbor
- ApproxBiLinear
- BiLinear
- CatmullRom
詳しくは調べきれていませんが、上記の上から処理スピードは早いが画質が悪い順になっていて、1番画質が良いのはCatmullRomですが、処理スピードは1番遅いとのことです。
また、画像のリサイズには、nfnt/resizeというパッケージもあり、使い方も簡単で、githubのStar数も少なくないですが、現在メンテナンスがされていないようです。
最後に
私は実際にこのコードを業務の最中で書いたのですが、その際に以下の記事を参考にしました。とても助かりました。ありがとうございます。
参考記事