Go ブログ
Go の image パッケージ
はじめに
image パッケージと image/color パッケージは、いくつかの型を定義しています。color.Color
と color.Model
は色を、image.Point
と image.Rectangle
は基本的な 2D 幾何学を、そして image.Image
はこれら2つの概念を組み合わせて色の長方形グリッドを表します。別の記事では、image/draw パッケージを使用した画像合成について説明します。
色とカラーモデル
Color は、色と見なすことができる任意の型の最小メソッドセットを定義するインターフェースです。CMYK や YCbCr などのカラー空間からの変換は、損失が発生する可能性があります。
type Color interface {
// RGBA returns the alpha-premultiplied red, green, blue and alpha values
// for the color. Each value ranges within [0, 0xFFFF], but is represented
// by a uint32 so that multiplying by a blend factor up to 0xFFFF will not
// overflow.
RGBA() (r, g, b, a uint32)
}
戻り値には3つの重要な微妙な点があります。まず、赤、緑、青はアルファプリマルチプライドされています。完全に飽和した赤で25%透明なものは、RGBA が75%の r を返すことで表されます。第二に、チャンネルは16ビットの実効範囲を持ちます。100%の赤は、RGBA が r として 65535 を返し、255 を返さないことで表され、CMYK や YCbCr からの変換の損失が少なくなります。第三に、最大値が 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
// The resultant red value is a blend of dstr and srcr, and ranges in [0, M].
// The calculation for green, blue and alpha is similar.
dstr = (dstr*(M-m) + srcr*m) / M
アルファプリマルチプライドされていない色を使用した場合、コードスニペットの最後の行はもっと複雑になっていたでしょう。そのため、Color
はアルファプリマルチプライドされた値を使用しています。
image/color パッケージは、Color
インターフェースを実装するいくつかの具体的な型も定義しています。たとえば、RGBA
は、古典的な「チャンネルあたり8ビット」の色を表す構造体です。
type RGBA struct {
R, G, B, A uint8
}
RGBA
の R
フィールドは、[0, 255] の範囲の 8 ビットのアルファプリマルチプライドされた色であることに注意してください。RGBA
は、その値に 0x101 を掛けて [0, 65535] の範囲の 16 ビットのアルファプリマルチプライドされた色を生成することで、Color
インターフェースを満たします。同様に、NRGBA
構造体型は、PNG 画像形式で使用される 8 ビットのアルファプリマルチプライドされていない色を表します。NRGBA
のフィールドを直接操作する場合、値はアルファプリマルチプライドされていませんが、RGBA
メソッドを呼び出すと、戻り値はアルファプリマルチプライドされます。
Model
は、Color
を他の Color
に変換できるものであり、場合によっては損失が発生する可能性があります。たとえば、GrayModel
は任意の Color
を脱色された Gray
に変換できます。Palette
は、任意の Color
を制限されたパレットからの1つに変換できます。
type Model interface {
Convert(c Color) Color
}
type Palette []Color
点と長方形
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
について、p.In(r)
は、r.Min.X <= p.X && p.X < r.Max.X
の場合、そして Y
についても同様です。これは、スライス s[i0:i1]
が下端では包含的で上端では排他的である方法に似ています。(配列やスライスとは異なり、Rectangle
は多くの場合、ゼロ以外の原点を持っています。)

r := image.Rect(2, 1, 5, 5)
// Dx and Dy return a rectangle's width and height.
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つの長方形を交差させると、空の可能性もある別の長方形が生成されます。

r := image.Rect(0, 0, 4, 3).Intersect(image.Rect(2, 2, 5, 5))
// Size returns a rectangle's width and height, as a Point.
fmt.Printf("%#v\n", r.Size()) // prints image.Point{X:2, Y:1}
点と長方形は値渡しで渡され、返されます。Rectangle
引数を受け取る関数は、2つの Point
引数または4つの int
引数を受け取る関数と同じくらい効率的です。
画像
Image は、Rectangle
内のすべてのグリッド正方形を、Model
からの Color
にマッピングします。「(x, y) のピクセル」とは、点 (x, y)、(x+1, y)、(x+1, y+1)、(x, y+1) で定義されるグリッド正方形の色を指します。
type Image interface {
// ColorModel returns the Image's color model.
ColorModel() color.Model
// Bounds returns the domain for which At can return non-zero color.
// The bounds do not necessarily contain the point (0, 0).
Bounds() Rectangle
// At returns the color of the pixel at (x, y).
// At(Bounds().Min.X, Bounds().Min.Y) returns the upper-left pixel of the grid.
// At(Bounds().Max.X-1, Bounds().Max.Y-1) returns the lower-right one.
At(x, y int) color.Color
}
よくある間違いは、Image
の境界が (0, 0) から始まると仮定することです。たとえば、アニメーション GIF には一連の Image が含まれており、最初の 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 holds the image's pixels, in R, G, B, A order. The pixel at
// (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*4].
Pix []uint8
// Stride is the Pix stride (in bytes) between vertically adjacent pixels.
Stride int
// Rect is the image's bounds.
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
実装は、SubImage
メソッドも提供します。これは、同じ配列によってバックアップされた Image
を返します。サブ画像のピクセルを変更すると、元の画像のピクセルにも影響します。これは、サブスライス 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 converts from JPEG to 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 関数でその形式を登録し、メインパッケージはその形式登録の副作用のためにのみ、そのようなパッケージを「アンダースコアインポート」します。
import (
"image"
"image/png"
"io"
_ "code.google.com/p/vp8-go/webp"
_ "image/jpeg"
)
// convertToPNG converts from any recognized format to 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)
}
次の記事: Go の image/draw パッケージ
前の記事: 反射の法則
ブログインデックス