Golang 圖片處理 — image 庫

bazinga發表於2021-07-02

編輯推薦:Bazinga

在開發中,有時會遇到對圖片的處理需求,在 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方法各個引數含義如下:

  • dst 繪圖的背景圖
  • r 背景圖的繪圖區域
  • src 要繪製的圖
  • sp src 對應的繪圖開始點
  • op 組合方式

以下程式碼是將一個 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)

Porter-Duff blend 模式 - Xamarin | Microsoft Docs

更多原創文章乾貨分享,請關注公眾號
  • Golang 圖片處理 — image 庫
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章