Golang 圖片處理 — image 庫

在開發中,有時會遇到對圖片的處理需求,在 Python 中, PIL/Pillow  庫非常強大和易用。

而 Golang 語言中,處理圖片的標準庫 image也可以實現一些基本操作。

image 庫支持常見的 PNG、JPEG、GIF 等格式的圖片處理, 可以對圖片進行讀取、裁剪、繪製、生成等操作。

讀取、新建圖片

讀取

圖片的讀取,和文件的讀取類似,只需要使用 os.Open()函數,獲取一個輸入流,然後將數據流進行解碼操作。

需要注意的是,在解碼階段,要將不同類型的圖片的解碼器先進行註冊,這樣纔不會報unknown format 的錯誤。

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 方法返回的第一個值是一個 image.Image類型接口。不同的顏色模型的圖片返回不同類型的值。該接口有三個方法:

type Image interface {
  ColorModel() color.Model // 返回圖片的顏色模型
  Bounds() Rectangle     // 返回圖片的長寬
  At(x, y int) color.Color // 返回(x,y)像素點的顏色
}

image 庫中很多結構都實現了該接口,對於一些標準庫中沒有實現的功能,我們也可以自己實現該接口去滿足。

新建

如果是需要新建一個圖片,可以使用image.NewRGBA()方法。

img := image.NewRGBA(image.Rect(0, 0, 300, 300))

這裏的 NewRGBA方法返回的是一個實現了 image.Image接口的 image.RGBA類型數據。這裏是一個 300*300 的透明背景的圖片。

保存圖片

保存圖片和保存文件也類似,需要先將圖片編碼,然後以數據流的形式寫入文件。

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))

該方法將從創建的 300 * 300 的圖片裁剪出 20 * 20 像素的子圖片。

繪製圖片

繪製圖片主要使用到 draw.Drawdraw.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方法各個參數含義如下:

以下代碼是將一個 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方法多了一個遮罩蒙層參數mask,以及蒙層的起始位置參數 mp

Draw方法是 DrawMask的一種特殊形式,當 DrawMaskmask 參數爲 nil 時,即爲Draw方法。

DrawMask將背景圖上的繪圖區域起始點、要繪製圖的起始點、遮罩蒙層的起始點進行對齊,然後對背景圖上的繪圖矩陣區域執行 Porter-Duff 合併操作。

下面是給圖片加一個圓形遮罩的示例:

func drawCirclePic() {
 f, err := os.Open("./gopher.png")
 if err != nil {
  panic(err)
 }
 gopherImg, _, err := image.Decode(f)


 d := gopherImg.Bounds().Dx()
 
 //將一個cicle作爲蒙層遮罩,圓心爲圖案中點,半徑爲邊長的一半
 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 { // 這裏需要自己實現一個圓形遮罩,實現接口裏的三個方法
 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{A: 255}
 }
 return color.Alpha{}
}

給圖片加一個圓角遮罩的示例:

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)
}

// 對每個像素點進行色值設置,分別處理矩形的四個角,在四個角的內切圓的外側,色值設置爲全透明,其他區域不透明
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}
}

在圖案進行圓形、圓角繪製的過程中,因爲最小單位是 1px,所以可能會有鋸齒邊緣的問題,解決這個問題可以通過先將原圖放大,遮罩後再縮小來解決。

**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 中國社區:https://gocn.vip/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/6q2OHKxdLRmLlrK-aa7vzA