LoginSignup
3
1

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-12-12

はじめに

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

最後に

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

3
1
0

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
  3. You can use dark theme
What you can do with signing up
3
1