Go 部落格
Go 影像包
引言
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 返回的 r 值是 75%(而不是 100%)。第二,通道具有 16 位的有效範圍:100% 的紅色透過 RGBA 返回的 r 值是 65535,而不是 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
欄位是 8 位 Alpha 預乘顏色,範圍在 [0, 255] 之間。RGBA
透過將該值乘以 0x101 來生成 16 位 Alpha 預乘顏色(範圍在 [0, 65535] 之間),從而滿足 Color
介面。類似地,NRGBA
結構體型別表示 8 位非 Alpha 預乘顏色,這與 PNG 影像格式使用的顏色相同。直接操作 NRGBA
的欄位時,值是非 Alpha 預乘的,但呼叫 RGBA
方法時,返回值是 Alpha 預乘的。
Model
僅僅是能夠將 Color
s 轉換為其他 Color
s 的東西,可能會丟失精度。例如,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
中的每個網格單元對映到一個 Color
,來自一個 Model
。“(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
通常只包含更改區域的畫素資料,並且該區域不一定從 (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 包
上一篇文章:反射定律
部落格索引