search
LoginSignup
2

More than 3 years have passed since last update.

posted at

Go言語で複数の画像を縦に連結する

0. はじめに

Go言語を用いて複数枚の画像連結を自動化したかったため、Goの image package を利用して実装しました。

他のQiita記事やブログ記事なども探しましたが、日本語での記事は見当たらなかったため、簡単な内容ではありますが書き残したいと思います。

1. 要件

  • 入力: Localに保存された2枚以上の画像

  • 出力: 入力された複数の画像を縦に連結した1枚の画像

2. アルゴリズム要約

  • 複数画像の読み込み
  • 出力画像の横幅・縦幅の算出
    • 横幅: 入力画像の横幅の最大値
    • 縦幅: 入力画像の縦幅の合計
  • 上記横幅・縦幅に基づいた空白の出力画像の生成
  • 画像を一枚ずつ出力画像に書き込み

3. 実装

複数画像の読み込み

読み込みは image packageの、image.Decode()image.DecodeConfig()を用いて行います。

// path: input image file path
func getImage(path string) (image.Image, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer file.Close()

    img, _, err := image.Decode(file)
    if err != nil {
        return nil, err
    }

    return img, nil
}
func getImageConfig(path string) (image.Config, error) {
    file, err := os.Open(path)
    if err != nil {
        return image.Config{}, err
    }
    defer file.Close()

    config, _, err := image.DecodeConfig(file)
    if err != nil {
        return image.Config{}, err
    }

    return config, nil
}

今回は image.Config のうち Width および Height を繰り返し用いるため、 myImage struct を定義しました。

type myImage struct {
    img    image.Image
    width  int
    height int
}

func getMyImage(path string) (myImage, error) {
    img, err := getImage(path)
    if err != nil {
        return myImage{}, err
    }

    config, err := getImageConfig(path)
    if err != nil {
        return myImage{}, err
    }

    return myImage{img, config.Width, config.Height}, nil
}

実はここで一つハマりポイントがあって、 os.Open() した file に対して下記のように image.Decode() と image.DecodeConfig() すると unknown format とエラーになります。

file, _ := os.Open(path)
img, _, _ := image.Decode(file)
config, _, _ := image.DecodeConfig(file) // image: unknown format

参考: http://suguru03.hatenablog.com/entry/2016/06/23/105152

今回扱う画像は高々10枚程度なので2度 file を Open する択を取りましたが、要件によってはネックとなるかもしれません。

出力画像の横幅・縦幅の算出

  • 横幅
func getMaxWidth(imgs []myImage) int {
    var res int
    for _, img := range imgs {
        if res < img.width {
            res = img.width
        }
    }

    return res
}
  • 縦幅
func getSumHeight(imgs []myImage) int {
    var res int
    for _, img := range imgs {
        res += img.height
    }

    return res
}

上記横幅・縦幅に基づいた空白の出力画像の生成

image.NewRGBA を用いて空白の出力画像を生成します

// imgs: []myImage
outImgWidth := getMaxWidth(imgs)
outImgHeight := getSumHeight(imgs)

outImg := image.NewRGBA(image.Rect(0, 0, outImgWidth, outImgHeight))

画像を一枚ずつ出力画像に書き込み

image/draw package の draw.Draw() を用いて、先ほどの出力画像に読み込んだ画像を書き込んでいきます。

pos := 0
for _, img := range imgs {
    rect := image.Rect(0, pos, img.width, pos+img.height)
    draw.Draw(outImg, rect, img.img, image.Point{0, 0}, draw.Over)
    pos += img.height
}

各説明

  • pos: 出力画像における、直前に書き込んだ画像の下端のy座標を記録し、次の書き込みの開始y座標として利用します
  • rect: draw.Draw に渡す引数で、出力画像におけるどの範囲に書き込むかを指定します
  • image.Point{0, 0}: 入力画像のどの座標から書き込むかを draw.Draw に伝えます。ex) もし「画像の下半分のみ書き込みたい」なら image.Point{0, img.height/2}

スクリーンショット 2019-07-06 16.37.24.png

以上の連結された画像を生成する実装をまとめると以下のようになります。

func createConcatImage(imgs []myImage) *image.RGBA {
    outImgWidth := getMaxWidth(imgs)
    outImgHeight := getSumHeight(imgs)

    outImg := image.NewRGBA(image.Rect(0, 0, outImgWidth, outImgHeight))

    pos := 0
    for _, img := range imgs {
        rect := image.Rect(0, pos, img.width, pos+img.height)
        draw.Draw(outImg, rect, img.img, image.Point{0, 0}, draw.Over)
        pos += img.height
    }

    return outImg
}

4. おわりに

以上が複数枚画像を連結する実装になります。
要件の都合上Github等で共有し辛かったため、画像連結の実装部分をGo Playgroundで共有します。(ただし画像を読み込む必要があるためそのままでは実行できません。)

駄文・長文にお付き合い頂きありがとうございました。
少しでもご参考になれば幸いです。

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
2