emahiro/b.log

日々の勉強の記録とか育児の記録とか。

Go で画像を操作する

Overview

業務において Go で画像を操作する方法を調べたので備忘録。Go の標準パッケージにある image package は必要最低限の機能を十分に備えてて便利だなと思いました。

画像の拡張子を操作する

拡張子を変換する。

同僚のブログが参考になりました。

rennnosukesann.hatenablog.com

拡張子を判別する

import (
   // blank import しないと format の判別ができないので注意
    _ "image/gif"
    _ "image/jpeg"
    _ "image/png"
)

url := "$SomeImageURL.jpg"
response, err := http.Get(url)
if err != nil {
    panic(err)
}

defer response.Body.Close()
_, format, err := image.Decode(response.Body)
if err != nil {
    fmt.Println(err)
    return
}
fmt.Println(format) // jpg

判別も簡単にできます。コメントに書いてますが、判別したいフォーマットを blank import していないと unknown になってしまいます。

http.DetectContentType で拡張子を判定する

http package には DetectContentType というバイト配列から画像フォーマットを指定する機能があります。

DetectContentType implements the algorithm described at https://mimesniff.spec.whatwg.org/ to determine the Content-Type of the given data. It considers at most the first 512 bytes of data. DetectContentType always returns a valid MIME type: if it cannot determine a more specific one, it returns "application/octet-stream".

base64 にされた画像の拡張子判定の実装は以下のような感じです。

b, err := base64.StdEncoding.DecodeString("$base64encodedString")
if err != nil {
    panic(err)
}
format = http.DetectContentType(b) // image/png etc...

image package での encode/decode を要しないので http.DetectContentType はメモリに優しいです。
なお、画像の拡張子については実際の判別してるコードを見ると

&exactSig{[]byte("\x89PNG\x0D\x0A\x1A\x0A"), "image/png"},
&exactSig{[]byte("\xFF\xD8\xFF"), "image/jpeg"},

ref: https://cs.opensource.google/go/go/+/refs/tags/go1.17.7:src/net/http/sniff.go;l=125-126;drc=refs%2Ftags%2Fgo1.17.7

という形で先頭の数バイトで一般的な画像の拡張子を判定しています。そのため実際に実装する場合には base64 エンコードされた文字列の先頭数バイトを見ればいいわけです。つまり上記のコードは以下のように変更することができます。

data := "$base64encodedString"
encLen := base64.StdEncoding.EncodedLen(8) // 先頭8バイトを読み込んで判別する。
b, err := base64.StdEncoding.DecodeString(data[:encLen])
if err != nil {
     panic(err)
}
format = http.DetectContentType(b) // image/png etc...

画像を作成する

サクッと画像を生成することもできます。
単体テストで画像を使うケースなど便利かなと思います。

buf := bytes.NewBuffer([]byte{})
img := image.NewRGBA(image.Rect(0, 0, 50, 50))
_ = png.Encode(buf, img)

// 書き出した画像を base64 文字列に変換
enc := base64.StdEncoding.EncodeToString(buf.Bytes())
fmt.Println(enc)

Encode/Decode する

base64

Go Tour が詳しいです。

oohira.github.io