使用Golang创建震撼的海报图

发表时间: 2024-07-02 17:37

为了传播拉新,需要海报图。一般情况下是前端生成:Canvas。但是,这次重担交给服务端了,能不能胜任?能,只要时间足够充裕,我可以慢慢学,调试(你看看,这工作态度。绝对的优秀员工)


相关库

  1. github.com/disintegration/imaging
  2. github.com/fogleman/gg
  3. golang.org/x/image/font
  4. golang.org/x/image/font/opentype
  5. image/color

github.com/disintegration/imaging

Imaging 是一个强大的图像处理库,提供了各种图像操作的功能,包括调整大小、裁剪、旋转、模糊、锐化、颜色调整等。

主要功能:

  • Resize: 调整图像大小。
  • Crop: 裁剪图像。
  • Rotate: 旋转图像。
  • Blur: 模糊图像。
  • Sharpen: 锐化图像。
  • Adjust: 调整亮度、对比度、饱和度等。

github.com/fogleman/gg

gg 是一个简单的 2D 绘图库,基于 cairo 提供了更方便的 API,用于生成和操作图形。它特别适合生成图表、图形叠加、文字和图形组合等任务。

主要功能:

  • 绘制基本形状: 线条、矩形、圆形、多边形等。
  • 文本处理: 设置字体、字号、颜色,并绘制文本。
  • 图像合成: 将多张图片进行组合。
  • 图像处理: 旋转、缩放、裁剪等。

golang.org/x/image/font

font 提供了与字体相关的功能,包括字体加载、字体渲染等。这个包定义了字体的基本接口和数据结构

主要功能:

  • Font face: 定义和处理字体面。
  • Drawers: 用于在图像上绘制文本。
  • Glyph: 提供了处理字体字形的基本操作

golang.org/x/image/font/opentype

opentype 专门用于处理 OpenType 字体格式,提供了加载和渲染 OpenType 字体的功能。

主要功能:

  • Parse: 解析 OpenType 字体文件。
  • NewFace: 创建字体面,以便于字体渲染。
  • Font metrics: 提供字体的度量信息(如升部、降部、行高等)。

image/color

image/color 包定义了表示颜色的接口和类型。它提供了各种颜色模型和用于转换颜色的实用函数。

主要功能:

  • Color model: 提供 RGBA、NRGBA、YCbCr、Gray 等颜色模型。
  • 色彩转换: 在不同颜色模型之间进行转换。
  • 颜色定义: 定义基本颜色如黑色、白色、红色等。

实现个小小需求

实现一个按钮,要求是椭圆型,文字居中。

参数信息:	boxW := 600	boxH := 300	boxColor := color.RGBA{R: 255, G: 255, B: 255, A: 1}	buttonW := 200	buttonH := 100	ButtonColor := "#8650ac"	fontSize := 48	fontDpi := 96.0	buttonText := "按钮"	fontColor := "#ffffff"	buttonImg := "./img/button.jpg"

物料:

下载字体资源文件:


技术方案

  1. 先拿一张宣纸(设定一个大的画布)
  2. 开始画椭圆(先画长方形,然后四个角加弧度,填充背景色)
  3. 拿个计算器,开始计算X,Y(计算文字行高)
  4. 拿起毛笔,开始在椭圆中心写字(加载字体,文字居中渲染)
  5. 按钮再覆盖到大的宣纸上(小画布和大画布相结合)
  6. 小demo实现

代码实现

package mainimport (	"context"	"fmt"	"github.com/disintegration/imaging"	"github.com/fogleman/gg"	"golang.org/x/image/font"	"golang.org/x/image/font/opentype"	"image/color"	"os")type DrawText struct {}type drawText interface {	// DrawButton 绘制按钮	DrawButton(ctx context.Context, w, y float64, color string, boxColor color.Color) (data *gg.Context, err error)	// LoadFont 加载字体	LoadFont(fontSize float64, dpi float64) (fontFace font.Face, err error)	// PxToPt PxToPt	PxToPt(px float64, dpi float64) float64	// SaveImg 保存图片	SaveImg(dc *gg.Context, fileName string) bool}func NewDrawText(ctx context.Context) drawText {	return &DrawText{}}func main() {	boxW := 600	boxH := 300	boxColor := color.RGBA{R: 255, G: 255, B: 255, A: 1}	buttonW := 200	buttonH := 100	ButtonColor := "#8650ac"	fontSize := 48	fontDpi := 96.0	buttonText := "按钮"	fontColor := "#ffffff"	buttonImg := "./img/button.jpg"	d := NewDrawText(context.Background())	// 创建一个大的画布	dc := gg.NewContext(boxW, boxH)	dc.SetColor(boxColor) // Set background color	dc.Clear()	// 绘制色块	dcButton, err := d.DrawButton(context.Background(), float64(buttonW), float64(buttonH), ButtonColor, boxColor)	if err != nil {		panic(err)		return	}	// 加载字体	fontFace, err := d.LoadFont(d.PxToPt(float64(fontSize), fontDpi), fontDpi)	if err != nil {		panic(err)		return	}	defer fontFace.Close()	// 设置字体参数、颜色	dcButton.SetFontFace(fontFace)	dcButton.SetHexColor(fontColor)	// 字体文案,计算x,y 居中位置	buttonTextW, buttonTextH := dcButton.MeasureString(buttonText)	dcButton.DrawString(buttonText, (float64(buttonW)-buttonTextW)/2, (float64(buttonH)+buttonTextH)*2/5)	// 按钮绘制到画布上	dc.DrawImage(dcButton.Image(), (boxW-buttonW)/2, (boxH-buttonH)/2)	if d.SaveImg(dc, buttonImg) {		return	}	fmt.Println("done")	return}func (d *DrawText) SaveImg(dc *gg.Context, fileName string) bool {	if err := imaging.Save(dc.Image(), fileName); err != nil {		panic(err)		return true	}	fmt.Printf("保持为jpg图片:%s\n", fileName)	return false}// LoadFont 加载字体func (d *DrawText) LoadFont(fontSize float64, dpi float64) (fontFace font.Face, err error) {	fontBytes, err := os.ReadFile("./assets/Source_Han_Sans_SC_Normal_Normal.otf")	if err != nil {		return nil, fmt.Errorf("failed to read font file: %w", err)	}	fontVal, err := opentype.Parse(fontBytes)	if err != nil {		return nil, fmt.Errorf("failed to parse font: %w", err)	}	fontFace, err = opentype.NewFace(fontVal, &opentype.FaceOptions{		Size:    fontSize,		DPI:     dpi,		Hinting: font.HintingFull,	})	if err != nil {		return nil, fmt.Errorf("failed to create font face: %w", err)	}	return fontFace, nil}// DrawButton 绘制色块func (d *DrawText) DrawButton(ctx context.Context, w, y float64, color string, boxColor color.Color) (data *gg.Context, err error) {	// 创建一个上下文来绘制色块	dc := gg.NewContext(int(w), int(y))	// 设置背景颜色 同画布颜色	dc.SetColor(boxColor)	// 填充颜色	dc.Clear()	// 设置圆角	dc.SetHexColor(color)	dc.DrawRoundedRectangle(0, 0, w, y, 20)	dc.Fill()	return dc, nil}func (d *DrawText) PxToPt(px float64, dpi float64) float64 {	return px * (72 / dpi)}

效果图

效果图

麻雀虽小,五脏俱全。想实现其他功能,自由组合。


总结:

海报图生成,最麻烦的就是计算,从始至终,就是拿着计算器计算,调整X,Y坐标。还有一点就是,px和pt的转化,不同的设备,分辨率展示出的效果不同。前端可以获取到客户端的设备信息,服务端拿不到这些数据,只能按照经验值去设定。


轻松一刻


我为人人,人人为我。美美与共,天下大同。