【GoCN酷Go推薦】Golang 圖片處理 — image 庫
在開發(fā)中,有時(shí)會(huì)遇到對(duì)圖片的處理需求,在 Python中, PIL/Pillow 庫非常強(qiáng)大和易用。
而 Golang 語言中,處理圖片的標(biāo)準(zhǔn)庫 image也可以實(shí)現(xiàn)一些基本操作。
image 庫支持常見的 PNG、JPEG、GIF 等格式的圖片處理, 可以對(duì)圖片進(jìn)行讀取、裁剪、繪制、生成等操作。
讀取、新建圖片
讀取
圖片的讀取,和文件的讀取類似,只需要使用 os.Open()函數(shù),獲取一個(gè)輸入流,然后將數(shù)據(jù)流進(jìn)行解碼操作。
需要注意的是,在解碼階段,要將不同類型的圖片的解碼器先進(jìn)行注冊(cè),這樣才不會(huì)報(bào)unknown format 的錯(cuò)誤。
package main
import (
"fmt"
"image"
_ "image/png"
"os"
)
func main() {
f, err := os.Open("./gopher.png")
if err != nil {
panic(err)
}
img, formatName, err := image.Decode(f)
if err != nil {
panic(err)
}
fmt.Println(formatName)
fmt.Println(img.Bounds())
fmt.Println(img.ColorModel())
}
Decode 方法返回的第一個(gè)值是一個(gè) image.Image類型接口。不同的顏色模型的圖片返回不同類型的值。該接口有三個(gè)方法:
type Image interface {
ColorModel() color.Model // 返回圖片的顏色模型
Bounds() Rectangle // 返回圖片的長寬
At(x, y int) color.Color // 返回(x,y)像素點(diǎn)的顏色
}
image 庫中很多結(jié)構(gòu)都實(shí)現(xiàn)了該接口,對(duì)于一些標(biāo)準(zhǔn)庫中沒有實(shí)現(xiàn)的功能,我們也可以自己實(shí)現(xiàn)該接口去滿足。
新建
如果是需要新建一個(gè)圖片,可以使用image.NewRGBA()方法。
img := image.NewRGBA(image.Rect(0, 0, 300, 300))
這里的 NewRGBA方法返回的是一個(gè)實(shí)現(xiàn)了 image.Image接口的 image.RGBA類型數(shù)據(jù)。這里是一個(gè)300*300的透明背景的圖片。
保存圖片
保存圖片和保存文件也類似,需要先將圖片編碼,然后以數(shù)據(jù)流的形式寫入文件。
img := image.NewRGBA(image.Rect(0, 0, 300, 300))
outFile, err := os.Create("gopher2.png")
defer outFile.Close()
if err != nil {
panic(err)
}
b := bufio.NewWriter(outFile)
err = png.Encode(b, img)
if err != nil {
panic(err)
}
err = b.Flush()
if err != nil {
panic(err)
}
裁剪圖片
圖片的裁剪主要使用SubImage()方法,如下:
img := image.NewRGBA(image.Rect(0, 0, 300, 300))
subImage := img.SubImage(image.Rect(0, 0, 20, 20))
該方法將從創(chuàng)建的300 * 300的圖片裁剪出20 * 20 像素的子圖片。
繪制圖片
繪制圖片主要使用到 draw.Draw和draw.DrawMask方法。
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)
Draw
Draw方法各個(gè)參數(shù)含義如下:
dst 繪圖的背景圖 r 背景圖的繪圖區(qū)域 src 要繪制的圖 sp src 對(duì)應(yīng)的繪圖開始點(diǎn) op 組合方式
以下代碼是將一個(gè) Gopher 的圖案繪制到了一張黑色背景空白圖的左上角。
f, err := os.Open("./gopher.png")
if err != nil {
panic(err)
}
gopherImg, _, err := image.Decode(f) // 打開圖片
img := image.NewRGBA(image.Rect(0, 0, 300, 300))
for x := 0; x < img.Bounds().Dx(); x++ { // 將背景圖涂黑
for y := 0; y < img.Bounds().Dy(); y++ {
img.Set(x, y, color.Black)
}
}
draw.Draw(img, img.Bounds(), gopherImg, image.Pt(0, 0), draw.Over) // 將gopherImg繪制到背景圖上

DrawMask
DrawMask方法多了一個(gè)遮罩蒙層參數(shù)mask,以及蒙層的起始位置參數(shù) mp。
Draw方法是 DrawMask的一種特殊形式,當(dāng) DrawMask 的 mask 參數(shù)為nil時(shí),即為Draw方法。
DrawMask將背景圖上的繪圖區(qū)域起始點(diǎn)、要繪制圖的起始點(diǎn)、遮罩蒙層的起始點(diǎn)進(jìn)行對(duì)齊,然后對(duì)背景圖上的繪圖矩陣區(qū)域執(zhí)行 Porter-Duff合并操作。
下面是給圖片加一個(gè)圓形遮罩的示例:
func drawCirclePic() {
f, err := os.Open("./gopher.png")
if err != nil {
panic(err)
}
gopherImg, _, err := image.Decode(f)
d := gopherImg.Bounds().Dx()
//將一個(gè)cicle作為蒙層遮罩,圓心為圖案中點(diǎn),半徑為邊長的一半
c := circle{p: image.Point{X: d / 2, Y: d / 2}, r: d / 2}
circleImg := image.NewRGBA(image.Rect(0, 0, d, d))
draw.DrawMask(circleImg, circleImg.Bounds(), gopherImg, image.Point{}, &c, image.Point{}, draw.Over)
SavePng(circleImg)
}
type circle struct { // 這里需要自己實(shí)現(xiàn)一個(gè)圓形遮罩,實(shí)現(xiàn)接口里的三個(gè)方法
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)
}
// 對(duì)每個(gè)像素點(diǎn)進(jìn)行色值設(shè)置,在半徑以內(nèi)的圖案設(shè)成完全不透明
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{A: 255}
}
return color.Alpha{}
}

給圖片加一個(gè)圓角遮罩的示例:
func drawRadiusPic() {
f, err := os.Open("./gopher.png")
if err != nil {
panic(err)
}
gopherImg, _, err := image.Decode(f)
w := gopherImg.Bounds().Dx()
h := gopherImg.Bounds().Dy()
c := radius{p: image.Point{X: w, Y: h}, r: int(40)}
radiusImg := image.NewRGBA(image.Rect(0, 0, w, h))
draw.DrawMask(radiusImg, radiusImg.Bounds(), gopherImg, image.Point{}, &c, image.Point{}, draw.Over)
SavePng(radiusImg)
}
type radius struct {
p image.Point // 矩形右下角位置
r int
}
func (c *radius) ColorModel() color.Model {
return color.AlphaModel
}
func (c *radius) Bounds() image.Rectangle {
return image.Rect(0, 0, c.p.X, c.p.Y)
}
// 對(duì)每個(gè)像素點(diǎn)進(jìn)行色值設(shè)置,分別處理矩形的四個(gè)角,在四個(gè)角的內(nèi)切圓的外側(cè),色值設(shè)置為全透明,其他區(qū)域不透明
func (c *radius) At(x, y int) color.Color {
var xx, yy, rr float64
var inArea bool
// left up
if x <= c.r && y <= c.r {
xx, yy, rr = float64(c.r-x)+0.5, float64(y-c.r)+0.5, float64(c.r)
inArea = true
}
// right up
if x >= (c.p.X-c.r) && y <= c.r {
xx, yy, rr = float64(x-(c.p.X-c.r))+0.5, float64(y-c.r)+0.5, float64(c.r)
inArea = true
}
// left bottom
if x <= c.r && y >= (c.p.Y-c.r) {
xx, yy, rr = float64(c.r-x)+0.5, float64(y-(c.p.Y-c.r))+0.5, float64(c.r)
inArea = true
}
// right bottom
if x >= (c.p.X-c.r) && y >= (c.p.Y-c.r) {
xx, yy, rr = float64(x-(c.p.X-c.r))+0.5, float64(y-(c.p.Y-c.r))+0.5, float64(c.r)
inArea = true
}
if inArea && xx*xx+yy*yy >= rr*rr {
return color.Alpha{}
}
return color.Alpha{A: 255}
}

在圖案進(jìn)行圓形、圓角繪制的過程中,因?yàn)樽钚挝皇?px,所以可能會(huì)有鋸齒邊緣的問題,解決這個(gè)問題可以通過先將原圖放大,遮罩后再縮小來解決。
Reference
The Go image/draw package - The Go Blog (golang.org)https://blog.golang.org/image-draw)
Porter-Duff blend 模式 - Xamarin | Microsoft Docs(https://docs.microsoft.com/zh-tw/xamarin/xamarin-forms/user-interface/graphics/skiasharp/effects/blend-modes/porter-duff)
歡迎加入我們GOLANG中國社區(qū):https://gocn.vip/
《酷Go推薦》招募:
各位Gopher同學(xué),最近我們社區(qū)打算推出一個(gè)類似GoCN每日新聞的新欄目《酷Go推薦》,主要是每周推薦一個(gè)庫或者好的項(xiàng)目,然后寫一點(diǎn)這個(gè)庫使用方法或者優(yōu)點(diǎn)之類的,這樣可以真正的幫助到大家能夠?qū)W習(xí)到
新的庫,并且知道怎么用。
大概規(guī)則和每日新聞?lì)愃疲绻麍?bào)名人多的話每個(gè)人一個(gè)月輪到一次,歡迎大家報(bào)名!(報(bào)名地址:https://wj.qq.com/s2/7734329/3f51)
掃碼也可以加入 GoCN 的大家族喲~
