The Go Blog

Go image/draw パッケージ

Nigel Tao
2011年9月29日

はじめに

image/draw パッケージは、1つの操作のみを定義しています。それは、オプションのマスク画像を通して、ソース画像をデスティネーション画像に描画することです。この1つの操作は驚くほど多用途で、多くの一般的な画像操作タスクをエレガントかつ効率的に実行できます。

合成は、Plan 9 グラフィックスライブラリと X Render 拡張機能のスタイルで、ピクセルごとに実行されます。このモデルは、Porter と Duff による古典的な「Compositing Digital Images」論文に基づいており、追加のマスクパラメータがあります: dst = (src IN mask) OP dst。完全に不透明なマスクの場合、これは元の Porter-Duff の式に還元されます: dst = src OP dst。Go では、nil マスク画像は、無限のサイズで完全に不透明なマスク画像と同等です。

Porter-Duff の論文では、12 種類の異なる合成演算子が提示されましたが、明示的なマスクを使用する場合、実際に必要なのは、ソースオーバーデスティネーションとソースの2つだけです。Go では、これらの演算子は Over および Src 定数で表されます。Over 演算子は、ソース画像をデスティネーション画像の上に自然に重ね合わせる操作を実行します。デスティネーション画像への変更は、ソース (マスク後) がより透明である (つまり、アルファ値が低い) ほど小さくなります。Src 演算子は、デスティネーション画像の元の内容を考慮せずに、ソース (マスク後) を単にコピーします。完全に不透明なソース画像とマスク画像の場合、両方の演算子は同じ出力を生成しますが、Src 演算子の方が通常高速です。

幾何学的なアライメント

合成には、デスティネーションピクセルとソースピクセルおよびマスクピクセルを関連付ける必要があります。当然、これにはデスティネーション、ソース、マスク画像、および合成演算子が必要ですが、さらに、各画像のどの矩形を使用するかを指定する必要もあります。すべての描画がデスティネーション全体に書き込むべきではありません。アニメーション画像を更新する場合、変更された部分のみを描画する方が効率的です。すべての描画がソース全体から読み取るべきではありません。多くの小さな画像を1つの大きな画像に結合するスプライトを使用する場合、画像の一部のみが必要です。すべての描画がマスク全体から読み取るべきではありません。フォントのグリフを集めたマスク画像はスプライトに似ています。したがって、描画には、各画像に1つずつ、3つの矩形を知る必要があります。各矩形は同じ幅と高さを持つため、デスティネーション矩形 r と2つの点 sp および mp を渡すだけで十分です。ソース矩形は、デスティネーション画像内の r.Min がソース画像内の sp と整列するように、r を平行移動したものです。mp についても同様です。有効な矩形は、それぞれの座標空間内の各画像の境界にもクリップされます。

DrawMask 関数は7つの引数を取りますが、明示的なマスクとマスクポイントは通常不要なため、Draw 関数は5つの引数を取ります。

// Draw calls DrawMask with a nil mask.
func Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point, op Op)
func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point,
 mask image.Image, mp image.Point, op Op)

デスティネーション画像は変更可能でなければならないため、image/draw パッケージは Set メソッドを持つdraw.Image インターフェースを定義しています。

type Image interface {
    image.Image
    Set(x, y int, c color.Color)
}

矩形を塗りつぶす

矩形を単色で塗りつぶすには、image.Uniform ソースを使用します。ColorImage 型は、Color を事実上無限サイズのその色の Image として再解釈します。Plan 9 の描画ライブラリの設計に慣れている人には、Go のスライスベースの画像型に明示的な「繰り返しビット」は必要ありません。その概念は Uniform に含まれています。

// image.ZP is the zero point -- the origin.
draw.Draw(dst, r, &image.Uniform{c}, image.ZP, draw.Src)

新しい画像をすべて青で初期化するには

m := image.NewRGBA(image.Rect(0, 0, 640, 480))
blue := color.RGBA{0, 0, 255, 255}
draw.Draw(m, m.Bounds(), &image.Uniform{blue}, image.ZP, draw.Src)

画像を透明にリセットするには (デスティネーション画像のカラーモデルが透明度を表現できない場合は黒)、image.Transparent を使用します。これは image.Uniform です。

draw.Draw(m, m.Bounds(), image.Transparent, image.ZP, draw.Src)

画像をコピーする

ソース画像内の矩形 sr から、デスティネーション内の点 dp から始まる矩形にコピーするには、ソース矩形をデスティネーション画像の座標空間に変換します。

r := image.Rectangle{dp, dp.Add(sr.Size())}
draw.Draw(dst, r, src, sr.Min, draw.Src)

あるいは

r := sr.Sub(sr.Min).Add(dp)
draw.Draw(dst, r, src, sr.Min, draw.Src)

ソース画像全体をコピーするには、sr = src.Bounds() を使用します。

画像をスクロールする

画像をスクロールすることは、異なるデスティネーションとソースの矩形を使用して、画像を自分自身にコピーすることです。Go の組み込みのコピー関数が重なり合うデスティネーションとソースのスライスを処理できるのと同様に、重なり合うデスティネーションとソースの画像は完全に有効です。画像 m を20ピクセルスクロールするには

b := m.Bounds()
p := image.Pt(0, 20)
// Note that even though the second argument is b,
// the effective rectangle is smaller due to clipping.
draw.Draw(m, b, m, b.Min.Add(p), draw.Src)
dirtyRect := b.Intersect(image.Rect(b.Min.X, b.Max.Y-20, b.Max.X, b.Max.Y))

画像をRGBAに変換する

画像形式のデコード結果は image.RGBA とは限りません。GIF のデコードは image.Paletted に、JPEG のデコードは ycbcr.YCbCr に、PNG のデコード結果は画像データに依存します。任意の画像を image.RGBA に変換するには

b := src.Bounds()
m := image.NewRGBA(image.Rect(0, 0, b.Dx(), b.Dy()))
draw.Draw(m, m.Bounds(), src, b.Min, draw.Src)

マスクを通して描画する

中心 p と半径 r を持つ円形マスクを通して画像を描画するには

type circle struct {
    p image.Point
    r int
}

func (c *circle) ColorModel() color.Model {
    return color.AlphaModel
}

func (c *circle) Bounds() image.Rectangle {
    return image.Rect(c.p.X-c.r, c.p.Y-c.r, c.p.X+c.r, c.p.Y+c.r)
}

func (c *circle) At(x, y int) color.Color {
    xx, yy, rr := float64(x-c.p.X)+0.5, float64(y-c.p.Y)+0.5, float64(c.r)
    if xx*xx+yy*yy < rr*rr {
        return color.Alpha{255}
    }
    return color.Alpha{0}
}

    draw.DrawMask(dst, dst.Bounds(), src, image.ZP, &circle{p, r}, image.ZP, draw.Over)

フォントのグリフを描画する

p から青色でフォントのグリフを描画するには、image.ColorImage ソースと image.Alpha マスクを使用して描画します。簡略化のため、サブピクセル配置やレンダリング、フォントのベースラインからの高さの補正は行いません。

src := &image.Uniform{color.RGBA{0, 0, 255, 255}}
mask := theGlyphImageForAFont()
mr := theBoundsFor(glyphIndex)
draw.DrawMask(dst, mr.Sub(mr.Min).Add(p), src, image.ZP, mask, mr.Min, draw.Over)

パフォーマンス

image/draw パッケージの実装は、汎用性がありながら、一般的なケースに対して効率的な画像操作関数を提供する方法を示しています。DrawMask 関数はインターフェース型の引数を取りますが、すぐにその引数が特定の構造体型であるという型アサーションを行います。これは、ある image.RGBA 画像を別の画像に描画したり、image.Alpha マスク (フォントのグリフなど) を image.RGBA 画像に描画したりするような一般的な操作に対応しています。型アサーションが成功した場合、その型情報を使用して汎用アルゴリズムの特殊化された実装を実行します。アサーションが失敗した場合、フォールバックコードパスは汎用的な At および Set メソッドを使用します。高速パスは純粋にパフォーマンス最適化であり、結果のデスティネーション画像はどちらの方法でも同じです。実際には、典型的なアプリケーションをサポートするために必要な特殊ケースは少数です。

次の記事: ブラウザでGoを学ぶ
前の記事: Go image パッケージ
ブログインデックス