まっしろけっけ

めもてきなやーつ

Golang で ImageMagick を使わずに画像をいじる

はじめに

最近動的に画像を変換するみたいなことをやっていて ImageMagick を使えば簡単にできるんですが
Golang の場合 ImageMagick を使わなくても様々な画像の加工が可能なので ImageMagick を使わずにやった時のメモ

画像形式の変換

最初は画像形式の変換の説明

jpg を png にしたり webp にしたりなどです。

ちなみに Golang では webp の Encode は標準でサポートされていないので今回は https://github.com/chai2010/webp を使用します。

package main

import (
	"flag"
	"fmt"
	"github.com/chai2010/webp"
	"image"
	"image/gif"
	"image/jpeg"
	"image/png"
	"os"
	"strings"
)

func main() {
	flag.Parse()
	args := flag.Args()

	f, err := os.Open(args[0]) // 元画像読み込み
	if err != nil {
		fmt.Println("open:", err)
		return
	}
	defer f.Close()

	img, _, err := image.Decode(f) // 元画像デコード
	if err != nil {
		fmt.Println("decode:", err)
		return
	}

	fso, err := os.Create(args[1]) // 変換後画像作成
	if err != nil {
		fmt.Println("create:", err)
		return
	}
	defer fso.Close()

	slice := strings.Split(args[1], ".")

	switch slice[len(slice)-1] { // 出力画像の拡張子によってエンコードを変える
	case "jpeg", "jpg":
		jpeg.Encode(fso, img, &jpeg.Options{})
	case "png":
		png.Encode(fso, img)
	case "gif":
		gif.Encode(fso, img, nil)
	case "webp":
		webp.Encode(fso, img, &webp.Options{Lossless: true})
	default:
	}
}

こんなコードを用意して下記のように実行すると変換されます。

$ go run main.go test.jpg hoge.png
$ go run main.go test.jpg hoge.webp
元画像 変換後(gif)
f:id:shiro-16:20200529113555j:plain f:id:shiro-16:20200529113617g:plain

画像の切り抜き

続いて画像の一部を切り抜く方法を説明

package main

import (
	"fmt"
	"image"
	"image/jpeg"
	"os"
)

type SubImager interface {
	SubImage(r image.Rectangle) image.Image
}

func main() {
	f, err := os.Open("test.jpg")
	if err != nil {
		fmt.Println("open:", err)
		return
	}
	defer f.Close()

	img, _, err := image.Decode(f)
	if err != nil {
		fmt.Println("decode:", err)
		return
	}

	fso, err := os.Create("out.jpg")
	if err != nil {
		fmt.Println("create:", err)
		return
	}
	defer fso.Close()

	cimg := img.(SubImager).SubImage(image.Rect(50, 0, 150, 100))

	jpeg.Encode(fso, cimg, &jpeg.Options{Quality: 100}) // Quality を指定しないと荒すぎる画像が出来上がるよ
}

image.Rect で横は 50px ~ 150px 縦は 0px ~ 100px まで切り取るという指定をしています。
こうして出来上がったのが下記の画像

元画像 切り取り後
f:id:shiro-16:20200529113555j:plain f:id:shiro-16:20200529115148j:plain

画像の合成

最後に画像の合成

2 つの画像を重ね合わせるなどして合成する方法を説明します。

package main

import (
	"fmt"
	"golang.org/x/image/draw"
	"image"
	"image/color"
	"image/jpeg"
	"os"
)

type SubImager interface {
	SubImage(r image.Rectangle) image.Image
}

func main() {
	f, err := os.Open("test.jpg")
	if err != nil {
		fmt.Println("open:", err)
		return
	}
	defer f.Close()

	img, _, err := image.Decode(f)
	if err != nil {
		fmt.Println("decode:", err)
		return
	}

	fso, err := os.Create("out.jpg")
	if err != nil {
		fmt.Println("create:", err)
		return
	}
	defer fso.Close()

	m := image.NewRGBA(image.Rect(0, 0, 200, 200)) // 200x200 の画像に test.jpg をのせる
	c := color.RGBA{0, 0, 255, 255} // RGBA で色を指定(B が 255 なので青)

	draw.Draw(m, m.Bounds(), &image.Uniform{c}, image.ZP, draw.Src) // 青い画像を描画

	rct := image.Rectangle{image.Point{25, 25}, m.Bounds().Size()} // test.jpg をのせる位置を指定する(中央に配置する為に横:25 縦:25 の位置を指定)

	draw.Draw(m, rct, img, image.Point{0, 0}, draw.Src) // 合成する画像を描画

	jpeg.Encode(fso, m, &jpeg.Options{Quality: 100})
}

こうして出来上がったのが下記の画像

元画像 合成後
f:id:shiro-16:20200529113555j:plain f:id:shiro-16:20200529122847j:plain

上記では青い画像を作成して合成を行なったがもちろん既存の画像を使うことも可能で下記のように書く

// 上記の defer fso.Close() まで略
        f2, _ := os.Open("hoge.jpg") // 元になる画像
        img2, _, _ := image.Decode(f2)

        rgba := image.NewRGBA(image.Rectangle{image.Point{0, 0}, image.Point{200, 200}}) // RGB形式の画像を用意する

        draw.Draw(rgba, image.Rectangle{image.Point{0, 0}, img2.Bounds().Size()}, img2, image.Point{0, 0}, draw.Src) // 元になる画像を描画する

        rct := image.Rectangle{image.Point{25, 25}, img2.Bounds().Size()} // 元画像への描画位置を決める

        draw.Draw(rgba, rct, img, image.Point{0, 0}, draw.Src) // 乗せる画像を描画

        jpeg.Encode(fso, rgba, &jpeg.Options{Quality: 100})
}

これで出来上がるのが下記の画像

元画像 合成する画像 合成後
f:id:shiro-16:20200529125808j:plain f:id:shiro-16:20200529113555j:plain f:id:shiro-16:20200529125837j:plain

終わりに

ちょっと長くなってしまったので今日はここまで
続きは次週とかに書くかもしれない