Go 部落格
Go image 包
引言
image 和 image/color 包定義了許多型別:color.Color
和 color.Model
描述顏色,image.Point
和 image.Rectangle
描述基本的二維幾何,而 image.Image
將這兩個概念結合起來表示一個矩形顏色網格。另一篇文章涵蓋了使用 image/draw 包進行的影像合成。
顏色和顏色模型
Color 是一個介面,它定義了任何可以被認為是顏色的型別的最小方法集:一個可以轉換為紅色、綠色、藍色和 alpha 值的方法集。這種轉換可能是有損的,例如從 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)
}
關於返回值有三個重要的細微之處。首先,紅色、綠色和藍色是預乘 alpha 值:一個完全飽和的紅色,同時具有 25% 的透明度,由 RGBA 返回 75% 的 r 來表示。其次,通道的有效範圍是 16 位:100% 的紅色由 RGBA 返回 65535 的 r 來表示,而不是 255,這樣從 CMYK 或 YCbCr 轉換的損耗就不會那麼大。第三,返回的型別是 uint32
,即使最大值是 65535,以保證兩個值相乘不會溢位。這種乘法發生在根據第三種顏色的 alpha 蒙版混合兩種顏色時,風格類似於 Porter 和 Duff 的經典代數。
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
如果使用非預乘 alpha 的顏色,上面的程式碼片段的最後一行會更復雜,這就是 Color
使用預乘 alpha 值的原因。
image/color 包還定義了許多實現了 Color
介面的具體型別。例如,RGBA
是一個表示經典“每通道 8 位”顏色的結構體。
type RGBA struct {
R, G, B, A uint8
}
注意,RGBA
的 R
欄位是一個範圍在 [0, 255] 的 8 位預乘 alpha 顏色。RGBA
透過將該值乘以 0x101 來滿足 Color
介面,生成一個範圍在 [0, 65535] 的 16 位預乘 alpha 顏色。類似地,NRGBA
結構體型別表示一個 8 位非預乘 alpha 顏色,如 PNG 影像格式所使用的那樣。直接操作 NRGBA
的欄位時,值是非預乘 alpha 的,但呼叫 RGBA
方法時,返回的值是預乘 alpha 的。
一個 Model
簡單來說就是可以將 Color
轉換為其他 Color
的東西,轉換過程可能是有損的。例如,GrayModel
可以將任何 Color
轉換為去飽和的 Gray
。Palette
可以將任何 Color
轉換為有限調色盤中的一種顏色。
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
,當且僅當 r.Min.X <= p.X && p.X < r.Max.X
且 Y 的條件類似時,p.In(r)
為真。這類似於切片 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
兩個矩形相交會產生另一個矩形,該矩形可能是空的。

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
引數的函式與一個接收兩個 Point
引數或四個 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
}
這些型別還提供了一個 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 包
上一篇文章:反射定律
部落格索引