Go image パッケージ
The Go image package By Nigel Tao
はじめに
image と image/color パッケージはいくつかの型を定義しています。color.Color と color.Model は色を、image.Point と image.Rectangle は基本的な2次元幾何学をそれぞれ記述しており、image.Image は色の長方形格子を表現するためにそれら2つの概念を1つにまとめます。別記事 では image/draw を用いた画像の合成について取り上げています。
Color と Color Model
Color は色として考えられる任意の型をまとめた最小限のメソッドの組を定義したインターフェースです。つまり、赤、緑、青、アルファ値に変換できるものです。CMYK や YCbCr 色空間から変換するといったある種の変換は不可逆かもしれません。
type Color interface {
// RGBA はこの色のプリマルチプライド・アルファな赤、緑、青、アルファの値を
// 返します。各値は [0, 0xFFFF] の範囲に収まりますが、0xFFFF までのブレンド
// 係数を掛けてもオーバーフローしないよう uint32 型で表現されています。
RGBA() (r, g, b, a uint32)
}
戻り値には3つの重要な注意点があります。1つ目に、赤、緑、青はプリマルチプライド・アルファであることです。25% 透明な完全飽和赤は 75% の r を返す RGBA によって表現されます。2つ目に、チャネルの有効範囲は 16 ビットです。100% の赤は 255 ではなく 65535 の r を返す RGBA によって表現されているので、CMYK や YCbCr からの変換はさほど情報量を失いません。3つ目に、最大値は 65535 ですが、戻り値の型は uint32 となっていることです。これは2つの値の積がオーバーフローしないことを保証するためです。こうした乗算は、Porter と Duff の古典的な代数のスタイルで、第3の色に由来するアルファマスクに従って2つの色をブレンドする際に発生します。
dstr, dstg, dstb, dsta := dst.RGBA()
srcr, srcg, srcb, srca := src.RGBA()
_, _, _, m := mask.RGBA()
const M = 1<<16 - 1
// 結果として得られる赤の値は dstr と srcr をブレンドしたもので、[0, M] の範囲に収まります。
// 緑、青、アルファの計算も同様です。
dstr = (dstr*(M-m) + srcr*m) / M
もし非プリマルチプライド・アルファを使って処理を行おうとすると、コード片の最後の行はより複雑になるでしょう。それが Color がプリマルチプライド・アルファな値を使う理由です。
image/color パッケージも Color インターフェースを実装するいくつかの具象型を定義しています。例えば、RGBA は古典的な “1 チャネルあたり 8 ビット” の色を表現する構造になっています。
type RGBA struct {
R, G, B, A uint8
}
RGBA の R フィールドは 0 から 255 の範囲の値をとる 8 ビットのプリマルチプライド・アルファな色であることに注意してください。0 から 65535 の範囲の値をとる 16 ビットのプリマルチプライド・アルファな色を生成するために 0x101 を値に掛けることによって RGBA は Color インターフェースを満たします。同様に、PNG の画像形式で使われるように NRGBA 構造型は 8 ビットの非プリマルチプライド・アルファな色を表現します。NRGBA のフィールドを直接操作する際は、その値は非プリマルチプライド・アルファですが、RGBA メソッドを呼ぶ際は、その戻り値はプリマルチプライド・アルファです。
Model は単純なもので、ある Color を、場合によっては非可逆的に、別の Color に変換できるものです。例えば、GrayModel は任意の Color を不飽和化された Gray に変換できます。Palette は任意の Color を、制限されたパレットの中の Color に変換できます。
type Model interface {
Convert(c Color) Color
}
type Palette []Color
Point と Rectangle
Point は右方向と下方向に増加する軸に従った整数格子上の (x, y) 座標です。それはピクセルでも格子で区切られた正方形でもありません。Point は固有の幅や高さ、色を持っていませんが、以下の図では色付きの小さな正方形を用いています。
type Point struct {
X, Y int
}

p := image.Point{2, 1}
Rectangle は左上と右下の Point によって定義される整数格子上にある軸に平行な長方形です。Rectangle も固有の色を持っていませんが、以下の図では色付きの細い線で長方形の輪郭を描き、その Min と Max の Point を示しています。
type Rectangle struct {
Min, Max Point
}
便利のため、image.Rect(x0, y0, x1, y1) は image.Rectangle{image.Point{x0, y0}, image.Point{x1, y1}} と同値ですが、前者のほうがはるかに入力しやすくなっています。
Rectangle は左上の点を包含し、右下の点を排他しています。Point p と Rectangle r に対し、r.Min.X <= p.X && p.X < r.Max.X を満たす場合のみ p.In(r) と表します。Y についても同様です。これは、スライス s[i0:i1] が下限を含み上限を排他するのと似ています。(配列やスライスと違い、Rectangle はよく非ゼロの原点を含みます。)

r := image.Rect(2, 1, 5, 5)
// Dx と Dy は長方形の幅と高さを返します。
fmt.Println(r.Dx(), r.Dy(), image.Pt(0, 0).In(r)) // prints 3 4 false
Point を Rectangle に加えることでその Rectangle を平行移動させます。点と長方形は右下の象限内に制限されていません。

r := image.Rect(2, 1, 5, 5).Add(image.Pt(-4, -2))
fmt.Println(r.Dx(), r.Dy(), image.Pt(0, 0).In(r)) // prints 3 4 true
2つの長方形の交わりはもう1つの長方形になりますが、それは空になることもあります。

r := image.Rect(0, 0, 4, 3).Intersect(image.Rect(2, 2, 5, 5))
// Size は長方形の幅と高さを Point として返します。
fmt.Printf("%#v\n", r.Size()) // prints image.Point{X:2, Y:1}
点と長方形は値渡しで関数に渡されたり返されたりします。Rectangle を引数に取る関数は、2つの Point あるいは4つの int を引数にとる関数と同じくらい効率的です。
Image
Image は Rectangle 内の格子で区切られた正方形を Model から作られる Color に射影します。"(x, y) のピクセル" は点 (x, y), (x+1, y), (x+1, y+1), (x, y+1) によって定義される格子で区切られた正方形の色を参照します。
type Image interface {
// ColorModel は Image のカラーモデルを返します。
ColorModel() color.Model
// Bounds は At が非ゼロの色を返しうる領域を返します。
// この範囲は必ずしも点 (0, 0) を含むとは限りません。
Bounds() Rectangle
// At は (x, y) にあるピクセルの色を返します。
// At(Bounds().Min.X, Bounds().Min.Y) は格子の左上のピクセルを返します。
// At(Bounds().Max.X-1, Bounds().Max.Y-1) は右下のピクセルを返します。
At(x, y int) color.Color
}
よくある誤りは Image の範囲が (0, 0) から始まると思い込むことです。例えば、アニメーション GIF は複数の Image からなる画像列を含みます。典型的には、最初の画像以降のそれぞれの Image は変化した領域のピクセルデータだけを保持しており、その領域は (0, 0) から始まるとは限りません。Image m のピクセル全体を走査する正しい方法は次のようになります。
b := m.Bounds()
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
doStuffWith(m.At(x, y))
}
}
Image の実装はピクセルデータのインメモリスライスを元にする必要はありません。例えば、Uniform は広大で一様な色の Image で、そのインメモリ表現は単なるその色です。
type Uniform struct {
C color.Color
}
それでもなお典型的に、プログラムはスライスベースの画像を求めています。RGBA や Gray のような構造型(他のパッケージでは image.RGBA や image.Gray として参照する)はピクセルデータのスライスを保持し Image インターフェースを実装します。
type RGBA struct {
// Pix は画像のピクセルを R, G, B, A の順に保持します。(x, y) のピクセルは
// Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*4] から始まります。
Pix []uint8
// Stride は縦に隣接するピクセル間の Pix のストライド(バイト数)です。
Stride int
// Rect は画像の範囲です。
Rect Rectangle
}
それらの型も一度に1ピクセルを修正する Set(x, y int, c color.Color) メソッドを提供します。
m := image.NewRGBA(image.Rect(0, 0, 640, 480))
m.Set(5, 5, color.RGBA{255, 0, 0, 255})
大量のピクセルデータを読み書きしたい場合、構造型の Pix フィールドに直接アクセスすると効率は上がりますが、その分コードは複雑になります。
スライスベースの Image の実装も同じ配列を元にした Image を返す SubImage メソッドを提供しています。サブスライス s[i0:i1] の内容を修正すると元のスライス s の内容に影響が及ぶことと同様に、サブ画像のピクセルを修正すると元の画像のピクセルに影響が及びます。

m0 := image.NewRGBA(image.Rect(0, 0, 8, 5))
m1 := m0.SubImage(image.Rect(1, 2, 5, 5)).(*image.RGBA)
fmt.Println(m0.Bounds().Dx(), m1.Bounds().Dx()) // prints 8, 4
fmt.Println(m0.Stride == m1.Stride) // prints true
画像の Pix フィールドを直接扱う下層のコードでは、Pix 全体を走査すると画像の範囲外のピクセルにまで影響が及ぶことがある点に注意してください。上記の例では、m1.Pix に含まれるピクセルが青色で示されています。At や Set メソッドまたは image/draw パッケージ のような上層のコードは、その操作を画像の範囲内に切り詰めます。
画像の形式
標準パッケージライブラリは GIF、JPEG、PNG など多くのありふれた画像形式をサポートしています。もとの画像ファイルのフォーマットが分かっている場合、io.Reader から直接デコードできます。
import (
"image/jpeg"
"image/png"
"io"
)
// convertJPEGToPNG は JPEG から PNG に変換します。
func convertJPEGToPNG(w io.Writer, r io.Reader) error {
img, err := jpeg.Decode(r)
if err != nil {
return err
}
return png.Encode(w, img)
}
形式不明な画像データがある場合、image.Decode 関数は形式を検出できます。認識される形式の集合は実行時に構築され、標準パッケージライブラリ内の形式に制限されていません。画像形式パッケージは典型的に init 関数内で自身の形式を登録し、main パッケージは形式登録という副作用だけを目的に、そのようなパッケージを “アンダースコアインポート” できます。
import (
"image"
"image/png"
"io"
_ "code.google.com/p/vp8-go/webp"
_ "image/jpeg"
)
// convertToPNG は認識可能な任意の形式から PNG に変換します。
func convertToPNG(w io.Writer, r io.Reader) error {
img, _, err := image.Decode(r)
if err != nil {
return err
}
return png.Encode(w, img)
}
By Nigel Tao
あわせて読む
- HTTP/2 Server Push
- Introducing HTTP Tracing
- Generating code
- Introducing the Go Race Detector
- Go maps in action
- go fmt your code
- Organizing Go code
- Debugging Go programs with the GNU Debugger
- The Go image/draw package
- The Laws of Reflection
- Error handling and Go
- “First Class Functions in Go”
- Profiling Go Programs
- Spotlight on external Go libraries
- A GIF decoder: an exercise in Go interfaces
- Introducing Gofix
- Godoc: documenting Go code
- Gobs of data
- C? Go? Cgo!
- JSON and Go
- Go Slices: usage and internals
- Go Concurrency Patterns: Timing out, moving on
- Defer, Panic, and Recover
- Share Memory By Communicating
- JSON-RPC: a tale of interfaces