Goブログ

Go image/draw パッケージ

Nigel Tao
2011年9月29日

はじめに

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

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

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

幾何学的配置

合成には、デスティネーションピクセルをソースピクセルおよびマスクピクセルに関連付ける必要があります。明らかに、これにはデスティネーション画像、ソース画像、マスク画像、および合成演算子が必要ですが、各画像の使用する長方形を指定する必要もあります。すべての描画がデスティネーション全体に書き込む必要はありません。アニメーション画像を更新する場合、変更された画像のパーツのみを描画する方が効率的です。すべての描画がソース全体から読み取る必要はありません。多くの小さな画像を1つの大きな画像に結合するスプライトを使用する場合、画像の一部だけが必要です。すべての描画がマスク全体から読み取る必要はありません。フォントのグリフを収集するマスク画像はスプライトに似ています。したがって、描画には、各画像に1つずつ、3つの長方形も知っている必要があります。各長方形の幅と高さが同じであるため、デスティネーション長方形rと2つの点spmpを渡せば十分です。ソース長方形は、デスティネーション画像の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のdrawライブラリの設計に詳しい人にとって、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.Uniformであるimage.Transparentを使用します。

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の組み込みのcopy関数が重複するデスティネーションスライスとソーススライスを処理できるのと同じです。画像を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 maskを使用して描画します。簡単にするために、サブピクセルのポジショニングやレンダリングは実行していません。また、フォントのベースラインからの高さの補正も行っていません。

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 パッケージ
ブログインデックス