【Go語言繪圖】圖片新增文字(二)

弗蘭克的貓發表於2021-08-01

想法來源

每次寫文章都得花點時間找圖,有點點麻煩,(其實就是懶。。。)。而且翻了翻之前的文章配圖,大概是這個樣子。

emmm,風格還挺統一的。但身為正義凜然的公眾號博主,老是用這樣圖,會有些圖文不符,也不符合我這正襟危坐的人物形象,而且,做為一名程式猿,能用程式碼解決的事情,自然要用程式碼來解決。
最近看到有些技術類公眾號,都使用了統一的圖片模版,比如這樣:

左邊放上logo,右邊放字,簡約大氣。於是,想著自己也整一個。

圖片分析

要如何生成這樣一張圖呢,我們來簡單來分析一下。

最底層是一張純色的底圖,這個不難,之前已經介紹過如何繪製純色底圖了,具體顏色可以用工具吸色看看。

第二層是文字,在前面的文章中,已經介紹過如何繪製文字,在這裡剛好能用上。

第三層是logo,還沒有介紹過將一張圖覆蓋在另一張圖上的操作,本文會對此進行講解。

這裡還需要注意的就是文字跟logo的區域,在繪製時,需要將文字框和圖片框的位置計算好,這個可以使用 Sketch 工具來完成。



這樣就得到了Logo和文字位置的具體資訊。

接下來就可以開始寫程式碼了

程式碼編寫

首先,我們先繪製一個純色的背景。RGB色值為:(63, 64, 87),底圖的寬和高通過 Sketch 工具測出分別為 1050和442。

import (
	"github.com/fogleman/gg"
	"testing"
)

func TestDrawWxPic(t *testing.T){
	weight := 1050
	height := 442
	dc := gg.NewContext(weight, height)
	dc.SetRGB255(63, 64, 87)
	dc.Clear()
	dc.SavePNG("out.png")
}

輸出圖片如下:

然後來繪製第二層的文字:

import (
	"github.com/fogleman/gg"
	"golang.org/x/image/font"
	"golang.org/x/image/font/opentype"
	"io/ioutil"
	"testing"
)

func TestDrawWxPic(t *testing.T){
	width := 1050
	height := 442
	dc := gg.NewContext(width, height)
	dc.SetRGB255(63, 64, 87)
	dc.Clear()

	fontFilePath := "/Users/bytedance/Downloads/FangZhengKaiTiJianTi.TTF"
	fontFace, err := getOpenTypeFontFace(fontFilePath, 76, 72)
	if err != nil {
		panic(err)
	}
	dc.SetFontFace(*fontFace)
	dc.SetRGB255(238, 241, 247)

	// 文字框最大寬度
	maxWordsWidth := 660.0
	x := 645.0
	y := 224.0
	dc.DrawStringWrapped("高併發的祕密武器 epoll 機制", x, y, 0.5, 0.6, maxWordsWidth, 1.1, gg.AlignCenter)
	dc.SavePNG("out.png")
}

func getOpenTypeFontFace(fontFilePath string, fontSize, dpi float64)(*font.Face, error){
	fontData, fontFileReadErr := ioutil.ReadFile(fontFilePath)
	if fontFileReadErr != nil {
		returnnil, fontFileReadErr
	}
	otfFont, parseErr := opentype.Parse(fontData)
	if parseErr != nil {
		returnnil, parseErr
	}
	otfFace, newFaceErr := opentype.NewFace(otfFont, &opentype.FaceOptions{
		Size: fontSize,
		DPI:  dpi,
	})
	if newFaceErr != nil {
		returnnil, newFaceErr
	}
	return &otfFace, nil
}

DrawStringWrapped 方法已經在之前的文章中介紹過了,這裡就不綴述了,文字框的位置可以通過 (x,y,ax,ay) 這四個引數來調整。
輸出效果如下:

字型不太一樣,位置是差不多了。

最後來把logo拼上去:

import (
	"github.com/disintegration/imaging"
	"github.com/fogleman/gg"
	"golang.org/x/image/font"
	"golang.org/x/image/font/opentype"
	"io/ioutil"
	"testing"
)

func TestDrawWxPic(t *testing.T){
	...
	dc.DrawStringWrapped("高併發的祕密武器 epoll 機制", x, y, 0.5, 0.6, maxWordsWidth, 1.1, gg.AlignCenter)

	//開始壓縮圖片
	src, openErr := imaging.Open("logo.png")
	if openErr != nil {
		panic(openErr)
	}
	src = imaging.Resize(src, 240, 240, imaging.Lanczos)
	dc.DrawImageAnchored(src, 182, 220, 0.5, 0.5)
	dc.SavePNG("out.png")
}

fogleman/gg 包不支援對圖片使用 resize 的操作,因此這裡引入了一個新包,github.com/disintegration/imaging。效果如下:

emm,看起來顏色有些不諧調,改一下背景色,然後放大一下logo試試:

嗯,這樣就好多了,把這段程式碼封裝成方法,傳入標題和檔案儲存位置,即可一鍵生成封面圖。
完整程式碼如下:

import (
	"github.com/disintegration/imaging"
	"github.com/fogleman/gg"
	"golang.org/x/image/font"
	"golang.org/x/image/font/opentype"
	"io/ioutil"
	"testing"
)

func TestDrawWxPic(t *testing.T){
	generateWxImage("微信公眾號封面圖\n一鍵生成", "result")
}

func generateWxImage(title string, savingFileName string) {
	width := 1050
	height := 442
	dc := gg.NewContext(width, height)
	dc.SetRGB255(47, 54, 66)
	dc.Clear()

	fontFilePath := "FangZhengKaiTiJianTi.TTF"
	fontFace, err := getOpenTypeFontFace(fontFilePath, 76, 72)
	if err != nil {
		panic(err)
	}
	dc.SetFontFace(*fontFace)
	dc.SetRGB255(238, 241, 247)

	// 文字框最大寬度
	maxWordsWidth := 660.0
	x := 665.0
	y := 224.0
	dc.DrawStringWrapped(title, x, y, 0.5, 0.6, maxWordsWidth, 1.1, gg.AlignCenter)

	//開始壓縮圖片
	src, openErr := imaging.Open("logo.png")
	if openErr != nil {
		panic(openErr)
	}
	src = imaging.Resize(src, 360, 360, imaging.Lanczos)

	dc.DrawImageAnchored(src, 182, 220, 0.5, 0.5)

	dc.SavePNG(savingFileName + ".png")
}

func getOpenTypeFontFace(fontFilePath string, fontSize, dpi float64)(*font.Face, error){
	fontData, fontFileReadErr := ioutil.ReadFile(fontFilePath)
	if fontFileReadErr != nil {
		returnnil, fontFileReadErr
	}
	otfFont, parseErr := opentype.Parse(fontData)
	if parseErr != nil {
		returnnil, parseErr
	}
	otfFace, newFaceErr := opentype.NewFace(otfFont, &opentype.FaceOptions{
		Size: fontSize,
		DPI:  dpi,
	})
	if newFaceErr != nil {
		returnnil, newFaceErr
	}
	return &otfFace, nil
}

小結

回顧一下整個過程,其實只要想清楚最終想要的效果,然後把步驟拆解開來,程式碼其實寫起來並不複雜,重要的還是邏輯,至於類似於如何實現圖片裁剪、文字拼接、圖片縮放,這些通常都有現成的庫可以拿來就用。
當然,如果感興趣也可以深入研究其中的實現原理。就像前面所說,瞭解這些工具,可以增加手中的牌,遇到問題時自然更加左右逢源。
最近的大部分時間都花在了健身上,很長時間都沒有好好寫部落格了,接下來得好好寫文章了,光有輸入確實還不夠,用輸出反推輸入,會更加有的放矢。

相關文章