業務内でGo言語を使って画像作成処理を実装する必要があったので、調査したことについて備忘録を兼ねて共有したいと思います。
画像作成
Go言語で画像を扱うにはimageライブラリを使用します。
例として、灰色の四角形内に複数の色付きの正方形を描いてみます。
package main
import (
"image"
"image/color"
"image/png"
"log"
"os"
)
func main() {
// 色の定義(RGBA値)
red := color.RGBA{255, 0, 0, 255} // 赤, 不透明度 Max 255 = 透過度ゼロ
blue := color.RGBA{0, 0, 255, 255} // 青, 不透明度 Max 255 = 透過度ゼロ
gray := color.RGBA{128, 128, 128, 255} // 灰, 不透明度 Max 255 = 透過度ゼロ
// 領域の定義(50pxの正方形)
width := 50
height := 50
myBound := image.Rect(0, 0, width, height)
// RGBAキャンバス作成
canvas := image.NewRGBA(myBound)
// キャンバス内をグレイで塗り潰す
for x := 0; x < width; x++ {
for y := 0; y < height; y++ {
canvas.Set(x, y, gray)
}
}
// 赤ボックスの描画(左上原点でキャンバスの 0,0 から 10x10pxの赤い点を打ち続けて四角を塗り潰し)
for x := 0; x < 10; x++ {
for y := 0; y < 10; y++ {
canvas.Set(x, y, red)
}
}
// 青ボックスの描画(左上原点でキャンバスの中央点から 10x10pxの青い点を打ち続けて四角を塗り潰し)
offsetX := width / 2
offsetY := width / 2
for x := 0; x < 10; x++ {
for y := 0; y < 10; y++ {
canvas.Set(x+offsetX, y+offsetY, blue)
}
}
file, err := os.Create("./test.png")
if err != nil {
log.Println("Cannot create file:", err)
}
defer file.Close()
png.Encode(file, canvas)
}
image.Rect(x0, y0, xMax, yMax)は、長方形のサイズを指定する関数です。引数のx0,y0は基本的に0でxMax、yMaxはそれぞれ図形の幅と高さをpxで設定します。
image.NewRGBA(rect)はアルファチャンネル付きのRGB(RGBA)の画像を作成する関数です。引数のrectには先ほどimage.Rectで作った長方形を指定します。画像データはimage.RGBA型の変数として扱われます。
color.RGBA(r, g, b, a)はRGBAの色を指定します。r、g、bは赤・緑・青の色情報、aは透明度を指定し、0~255の値で設定します。aの値が0の時は完全透明、255の時は完全不透明となります。
image.RGBAのSet(x, y, color)は画像上の指定した(x, y)座標に指定したcolorの色を描画する関数です。for文を合わせることで指定した範囲を塗りつぶすことができます。
作成した画像データは、png.Encode(file, canvas)、jpeg.Encode(file, canvas)で各画像フォーマットに変換して保存することが可能です。
実行結果
Web上の画像を取得して操作
Webやサーバー上の画像ファイルを読み込んで操作するには、golang.org/x/image/drawライブラリを使用します。
package main
import (
"bytes"
"image"
_ "image/jpeg"
"image/png"
_ "image/png"
"io"
"log"
"net/http"
"os"
"golang.org/x/image/draw"
)
func main() {
res, err := http.Get("{画像のURL}")
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
originImage, _, err := image.Decode(bytes.NewReader([]byte(body)))
newImage := image.NewRGBA(image.Rect(0, 0, originImage.Bounds().Dx(), originImage.Bounds().Dy()))
draw.BiLinear.Scale(
newImage,
newImage.Bounds(),
originImage,
originImage.Bounds(),
draw.Over,
nil,
)
file, err := os.Create("./download.png")
if err != nil {
log.Println("Cannot create file:", err)
}
defer file.Close()
png.Encode(file, newImage)
}
Web上の画像を取得するには、まずhttp.Getを使って画像データのHTTPレスポンスを取得します。続けてio.ReadAllを使ってレスポンスのbody情報を読み取り、image.Decodeすることで画像情報として取り扱えるようになります。
※ image.Decodeでunknown formatエラーが出る場合、import宣言部分に_ “image/jpeg” や _ “image/png” 等、読み込む画像のフォーマットに合わせたライブラリをアンダースコアで呼び出すことで解消します。
参考: https://qiita.com/hrk02/items/0d1a7f46dbcd21b081c6
読み込んだ画像(originImage)をimage.RGBA型のデータとして扱うには、まずoriginImageと同じサイズのimage.RGBAデータ(newImage)を作成します。
その後、draw.BiLinear.Scaleを用いて白紙のnewImageの上にoriginImageを上書きする形でimage.RGBA型データとして扱えるようにします。
draw.BiLinear.Scaleは引数にnewImage、newImageのサイズ、originImage、originImageのサイズ、オプションを指定します。今回の場合、オプションには上書きを指定するdraw.Overを指定します。
画像と画像の合成
ここまで紹介したファイルからの画像読み取りと画像作成を組み合わせることで、2つの画像を組み合わせることができます。
例として以下の画像を使って画像作成を行います。

※Go言語のマスコットキャラクター: Gopherくん(designed by Renée French)
package main
import (
"image"
"image/color"
_ "image/jpeg"
"image/png"
_ "image/png"
"os"
"golang.org/x/image/draw"
)
func main() {
white := color.RGBA{255, 255, 255, 255} // 白, 不透明度 Max 255 = 透過度ゼロ
red := color.RGBA{255, 0, 0, 255} // 赤, 不透明度 Max 255 = 透過度ゼロ
imageFile, _ := os.Open("./gopher.png")
defer imageFile.Close()
originImage, _, _ := image.Decode(imageFile)
newImage := image.NewRGBA(image.Rect(0, 0, originImage.Bounds().Dx(), originImage.Bounds().Dy()))
// 背景を白で塗りつぶす
for x := 0; x < newImage.Rect.Dx(); x++ {
for y := 0; y < newImage.Rect.Dy(); y++ {
newImage.Set(x, y, white)
}
}
draw.BiLinear.Scale(
newImage,
newImage.Bounds(),
originImage,
originImage.Bounds(),
draw.Over,
nil,
)
// 縮小した画像
small := image.NewRGBA(image.Rect(0, 0, originImage.Bounds().Dx()/2, originImage.Bounds().Dy()/2))
// 背景を赤で塗りつぶす
for x := 0; x < small.Rect.Dx(); x++ {
for y := 0; y < small.Rect.Dy(); y++ {
small.Set(x, y, red)
}
}
draw.BiLinear.Scale(
small,
small.Bounds(),
originImage,
originImage.Bounds(),
draw.Over,
nil,
)
draw.Draw(
newImage,
small.Bounds().Add(image.Pt(newImage.Bounds().Dx()/4, newImage.Bounds().Dy()/4)),
small,
image.Point{0, 0},
draw.Over,
)
file, _ := os.Create("./drawOver.png")
defer file.Close()
png.Encode(file, newImage)
}
まず、先ほどと同じようにimage.NewRGBAを使って画像サイズと同じスケールで白背景のimage.RGBAデータを作成し、その上に画像を貼り付けます。
このimage.RGBAをnewImageとします。
次に、image.NewRGBAを使って画像サイズの幅・高さが半分のスケールで赤背景のimage.RGBAデータを作成し、その上に画像を貼り付けます。
このimage.RGBAをsmallとします。
2つの画像(image.RGBAデータ)を合成するにはdraw.Draw関数を使います。今回はnewImageの上にsmallを重ねた画像を作成します。
draw.Draw関数の第1引数に下地になる画像(newImage)、第2引数に貼り付ける画像の位置、第3引数に貼り付ける画像(small)を指定します。
今回は貼り付ける位置としてnewImageの左上から幅・高さそれぞれ1/4の位置を指定し、smallを貼り付けるようにしました。
実行結果
※白背景Gopherくんの上に赤背景・縮小Gopherくんを重ねた画像
画像内に文字列を描画
画像内に文字列を表示するにはgolang.org/x/image/fontライブラリを利用します。
今回は「Hello, World! こんにちは、世界!」と書かれた画像を出力してみます。
package main
import (
"image"
"image/color"
_ "image/jpeg"
"image/png"
_ "image/png"
"os"
"golang.org/x/image/font"
"golang.org/x/image/font/basicfont"
"golang.org/x/image/math/fixed"
)
func main() {
white := color.RGBA{255, 255, 255, 255} // 白, 不透明度 Max 255 = 透過度ゼロ
// 領域の定義
width := 250
height := 100
myBound := image.Rect(0, 0, width, height)
// RGBAキャンバス作成
canvas := image.NewRGBA(myBound)
// キャンバス内を白で塗り潰す
for x := 0; x < width; x++ {
for y := 0; y < height; y++ {
canvas.Set(x, y, white)
}
}
// basicfontというフォントを使って文字を描く。
face := basicfont.Face7x13
d := &font.Drawer{
Dst: canvas,
Src: image.NewUniform(color.RGBA{0, 0, 0, 255}), // テキストの色。
Face: face,
Dot: fixed.Point26_6{X: fixed.I(20), Y: fixed.I(50)}, // テキストを描く位置。
}
d.DrawString("Hello, World! こんにちは、世界!")
file, _ := os.Create("./drawString.png")
defer file.Close()
png.Encode(file, canvas)
}
まず、image.NewRGBAを使って文字の描画先となるimage.RGBA型データのcanvasを作成します。
文字を描くためにはfont.Drawerの設定をします。Dstは文字の描画先(canvas)、Srcは文字の色、Faceはフォント、Dotは文字を描く位置を設定します。
今回はbasicfontというフォントを使います。また、Dotはfixed.Point26_6を使って設定します。
それぞれgolang.org/x/image/font/basicfontライブラリとgolang.org/x/image/math/fixedライブラリが必要なので事前にインポートしておきます。
font.DrawerのDrawString関数に文字列を渡すと、指定された画像データに対して文字列を描画することができます。
実行結果
※デフォルトのフォントで文字列を描画
出力された画像を確認すると日本語部分がうまく描画されておりません。
これはbasicfontが日本語に非対応のフォントのためです。
日本語で文字列を描画
日本語で文字を描画するには、日本語に対応しているフォントのフォントファイルを読み込む必要があります。
まず、事前にフォントファイルをダウンロードしておきます。
今回はKoruriフォントを利用していきます。
https://koruri.github.io/
ダウンロードしたzipファイルを展開し、その中にあるKoruri-Bold.ttfを実行するgoファイルと同じディレクトリに配置します。
前のコードのfaceおよびd := &font.Drawer の部分を以下のように書き換えます。
// 日本語対応したフォントを読み込んで文字を描く。
ttf, _ := os.ReadFile("./Koruri-Bold.ttf")
font_, _ := truetype.Parse(ttf)
face := truetype.NewFace(font_, &truetype.Options{
Size: 16, // フォントサイズ
})
d := &font.Drawer{
Dst: canvas,
Src: image.NewUniform(color.RGBA{0, 0, 0, 255}), // テキストの色。
Face: face,
Dot: fixed.Point26_6{X: fixed.I(20), Y: fixed.I(50)}, // テキストを描く位置。
}
d.DrawString("Hello, World! こんにちは、世界!")
フォントファイルの読み込みのためにはgithub.com/golang/freetype/truetypeライブラリが必要になります。
truetype.Parse関数を使ってフォントファイルを読み込み、truetype.NewFace関数でアプリ内でフォント情報として扱えるように変換します。また、NewFaceを呼び出す際にフォントサイズも設定できます。
あとはfont.DrawerのFaceに読み込んだフォント情報を渡すことで日本語文字列を描画することができます。
実行結果
まとめ
今回はGo言語による画像処理の基本と、画像作成・操作、および2つの画像の重ね合わせ方法・画像内に文字列を描画する方法について紹介しました。
Go言語での開発で画像処理を行う場合は参考にしていただけると幸いです。
参考
- image package – image – Go Packages: https://pkg.go.dev/image
- draw package – golang.org/x/image/draw – Go Packages: https://pkg.go.dev/golang.org/x/image/draw
- 【Golang】画像を扱ってみた: https://zenn.dev/a5chin/articles/implementation_goimg
- goで画像操作: https://zenn.dev/not75743/scraps/da30d8f30ad7eb
- 【Golang】image パッケージの Bounds とは(四角が描画された画像を作成したい): https://qiita.com/KEINOS/items/63a44fb1eb91b61f82a3
- Goで画像に日本語の文字を書く(自動折り返し付き): https://blanktar.jp/blog/2024/02/golang-write-text-on-image
- GO言語 DrawStringで画像に日本語を描画する : https://qiita.com/n-noguchi/items/566e83c5cc0d3b80852c



