LoginSignup
1

More than 1 year has passed since last update.

posted at

updated at

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

はじめに

画像のリサイズは、業務だと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数も少なくないですが、現在メンテナンスがされていないようです。

最後に

私は実際にこのコードを業務の最中で書いたのですが、その際に以下の記事を参考にしました。とても助かりました。ありがとうございます。
参考記事

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
1