Goでアスペクト比を保って画像のリサイズを行ってみる

## はじめに
画像のリサイズは、業務だと[imageFlux](https://www.sakura.ad.jp/services/imageflux/)を使ったりすることが多いかもしれません。
imageFluxなどを利用することができる環境であれば良いのですが、個人開発や、コストをかけたくないなどの理由で、Goを画像のリサイズを行う方法について書いてみました。


## 処理の流れ
1.画像データのデコードしてwidthとheightの値を取得する
2.画像のリサイズ処理

## 実際の処理
### 1. 画像データのデコードしてwidthとheightの値を取得する
画像データのデコードと、widthとheightの取得をコードで書くと以下のような感じになります。

```go
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などで取得した画像ファイルが入ります。

```go
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関数](https://golang.org/pkg/image/#RegisterFormat)が、image/jpegパッケージのinit関数で呼ばれることで初期化されています。

```go
// 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の取得のコードの続きになります。

```go
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定義部分](https://godoc.org/golang.org/x/image/draw#pkg-variables)

- NearestNeighbor
- ApproxBiLinear
- BiLinear
- CatmullRom

詳しくは調べきれていませんが、上記の上から処理スピードは早いが画質が悪い順になっていて、1番画質が良いのはCatmullRomですが、処理スピードは1番遅いとのことです。

また、画像のリサイズには、[nfnt/resize](https://github.com/nfnt/resize)というパッケージもあり、使い方も簡単で、githubのStar数も少なくないですが、現在メンテナンスがされていないようです。

## 最後に
私は実際にこのコードを業務の最中で書いたのですが、その際に以下の記事を参考にしました。とても助かりました。ありがとうございます。
[参考記事](https://text.baldanders.info/golang/resize-image/)