Go 部落格
Go image/draw 套件
簡介
套件 image/draw 僅定義一個操作:透過一個選擇性的遮罩影像,將一個來源影像繪製到一個目標影像上。這個操作 überraschend地具有多功能性,並且能夠優雅且有效率地執行許多常見的影像處理作業。
合成是以類似於 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
運算子執行來源影像在目標影像上方的自然分層:目標影像的變化在來源(遮罩後)透明度較高的部分較小(也就是 alpha 較低)。Src
運算子僅複製來源(遮罩後),而不用考慮目標影像的原始內容。對於完全不透明的來源和遮罩影像,這兩種運算子會產生相同的輸出,但 Src
運算子通常較快。
幾何對齊
合成需要將目標畫素與來源和遮罩畫素做關聯。顯然地,這需要目標、來源和遮罩影像,以及合成運算子,但它也需要指定要使用每張影像的哪個矩形。並非每一項繪圖都應該寫入整個目標:更新動態影像時,僅繪製影像中已變更的部分會比較有效率。並非每一項繪圖都應該讀取整個來源:當使用將許多小影像合併成一個大影像的圖塊時,只需要影像的一小部分。並非每一項繪圖都應該讀取整個遮罩:收集字型字形的遮罩影像類似於圖塊。因此,繪圖也需要知道三個矩形,每個影像一個。由於每個矩形的寬度和高度相同,因此只要傳遞目標矩形 r
和兩個點 sp
和 mp
即可:來源矩形等於轉換 r
使得目標影像中的 r.Min
與來源影像中的 sp
對齊,同理可套用於 mp
。實際矩形也裁剪到個別影像在它們自己座標空間中的邊界。

DrawMask
函數需要七個引數,但通常不需要明確遮罩和遮罩點,因此 Draw
函數只需要五個
// 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 套件定義了一個 draw.Image
介面,其中有一個 Set
方法。
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.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 內建的複製函式可以處理重疊的目標和來源切片。將影像 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 套件
部落格索引