gg库是一个功能强大的图片处理库,提供了画圆、画方、画线、填充、描边、旋转、缩放、文字处理、剪切、蒙版、翻转的接口。
gg库
要使用gg库,需要先安装go get -u github.com/fogleman/gg
。对应的接口说明文档https://pkg.go.dev/github.com/fogleman/gg
。
如:通过椭圆画花:
import "github.com/fogleman/gg"
func drawFlower() {
const S = 1024
dc := gg.NewContext(S, S)
dc.SetRGBA(0, 0, 0, 0.1)
for i := 0; i < 360; i += 15 {
dc.Push()
dc.RotateAbout(gg.Radians(float64(i)), S/2, S/2)
dc.DrawEllipse(S/2, S/2, S*7/16, S/8)
dc.Fill()
dc.Pop()
}
dc.SavePNG("D:\\temp\\out.png")
}
创建context
在使用库前,需要先创建一个context:
NewContext(width, height int) *Context
NewContextForImage(im image.Image) *Context
NewContextForRGBA(im *image.RGBA) *Context
Draw函数
一系列常见图形函数:
DrawPoint(x, y, r float64)
DrawLine(x1, y1, x2, y2 float64)
DrawRectangle(x, y, w, h float64)
DrawRoundedRectangle(x, y, w, h, r float64)
DrawCircle(x, y, r float64)
DrawArc(x, y, r, angle1, angle2 float64)
DrawEllipse(x, y, rx, ry float64)
DrawEllipticalArc(x, y, rx, ry, angle1, angle2 float64)
DrawRegularPolygon(n int, x, y, r, rotation float64)
DrawImage(im image.Image, x, y int)
DrawImageAnchored(im image.Image, x, y int, ax, ay float64)
SetPixel(x, y int)
MoveTo(x, y float64)
LineTo(x, y float64)
QuadraticTo(x1, y1, x2, y2 float64)
CubicTo(x1, y1, x2, y2, x3, y3 float64)
ClosePath()
ClearPath()
NewSubPath()
Clear() // Fill entire image with current color
Stroke()
Fill()
StrokePreserve()
FillPreserve()
DrawXXX函数只是创建了一系列的路径,只有调用Stoke函数后,才会使用当前设定(颜色、线宽等)完成实际的画图动作。
Text函数
在图片上添加文字:
DrawString(s string, x, y float64)
DrawStringAnchored(s string, x, y, ax, ay float64)
DrawStringWrapped(s string, x, y, ax, ay, width, lineSpacing float64, align Align)
MeasureString(s string) (w, h float64)
MeasureMultilineString(s string, lineSpacing float64) (w, h float64)
WordWrap(s string, w float64) []string
SetFontFace(fontFace font.Face)
LoadFontFace(path string, points float64) error
Color函数
设定颜色的函数:
SetRGB(r, g, b float64)
SetRGBA(r, g, b, a float64)
SetRGB255(r, g, b int)
SetRGBA255(r, g, b, a int)
SetColor(c color.Color)
SetHexColor(x string)
线与填充函数
设定线与填充:
SetLineWidth(lineWidth float64)
SetLineCap(lineCap LineCap)
SetLineJoin(lineJoin LineJoin)
SetDash(dashes ...float64)
SetDashOffset(offset float64)
SetFillRule(fillRule FillRule)
设定渐变与样式
SetFillStyle(pattern Pattern)
SetStrokeStyle(pattern Pattern)
NewSolidPattern(color color.Color)
NewLinearGradient(x0, y0, x1, y1 float64)
NewRadialGradient(x0, y0, r0, x1, y1, r1 float64)
NewSurfacePattern(im image.Image, op RepeatOp)
转换函数
各转换函数:
Identity()
Translate(x, y float64)
Scale(x, y float64)
Rotate(angle float64)
Shear(x, y float64)
ScaleAbout(sx, sy, x, y float64)
RotateAbout(angle, x, y float64)
ShearAbout(sx, sy, x, y float64)
TransformPoint(x, y float64) (tx, ty float64)
InvertY()
裁剪函数
裁剪相关函数:
Clip() // 更新裁剪区:更新为,当前Path范围与裁剪区的交集
ClipPreserve()
ResetClip()
AsMask() *image.Alpha
SetMask(mask *image.Alpha)
InvertMask()
辅助函数
Radians(degrees float64) float64
Degrees(radians float64) float64
LoadImage(path string) (image.Image, error)
LoadPNG(path string) (image.Image, error)
SavePNG(path string, im image.Image) error
保存当前context的状态:
Push()
Pop()
绘图
在使用库之前需要先初始化一个Context对象。
通过draw函数画图时,只有调用Stroke(或Fill)函数后,才会真正画(使用当前设置);
如:画一条蓝色的线,边框为蓝色的矩形;以及一条红色的线,填充为红色的矩形。
若把Stroke-1处的Stoke注释掉,则:
- 蓝色的线将不会画出:因Fill-2会填充所有闭合路径后,清除路径信息;
- 蓝色的矩形框会变为填充红色的矩形;
func drawShapes() {
width := 300
height := 300
dc := gg.NewContext(width, height)
// blue line
pos := 50.0
dc.SetRGB255(0, 0, 255)
dc.SetLineWidth(3)
dc.DrawRectangle(10, 190, 100, 100)
//dc.Fill()
dc.DrawLine(0, pos, float64(width), pos)
dc.Stroke() // Stroke-1
// red line
pos += 100
dc.SetRGB255(255, 0, 0)
dc.SetLineWidth(2)
dc.DrawRectangle(150, 190, 100, 100)
dc.Fill() // Fill-2
dc.DrawLine(0, pos, float64(width), pos)
dc.Stroke()
dc.SavePNG("D:\\temp\\out.png")
}
输出的图片样式:
裁剪
裁剪图片为圆形:
func drawCircleImg() {
img, err := gg.LoadImage("D:\\temp\\tmp.jpg")
if err != nil {
panic(err)
}
dc := gg.NewContext(img.Bounds().Dx(), img.Bounds().Dy())
// 画圆形
radius := math.Min(float64(img.Bounds().Dx()), float64(img.Bounds().Dy())) / 2
dc.DrawCircle(float64(img.Bounds().Dx()/2), float64(img.Bounds().Dy()/2), radius)
// 对画布进行裁剪
dc.Clip()
dc.DrawImage(img, 0, 0)
dc.SavePNG("D:\\temp\\out.png")
}
旋转
旋转使用Rotate()(绕左上角) 或 RotateAbout(绕指定点):
- 旋转是顺时针方向;
- 参数是弧度,需要通过Radians把角度转换为弧度;
func rotateImage() {
img, err := gg.LoadImage("D:\\temp\\tmp.jpg")
if err != nil {
panic(err)
}
width := 2 * img.Bounds().Dx()
height := 2 * img.Bounds().Dy()
dc := gg.NewContext(width, height)
dc.DrawRectangle(0, 0, float64(width), float64(width))
dc.SetRGB255(0, 255, 0)
dc.Fill()
dc.RotateAbout(gg.Radians(45), float64(width/2), float64(height/2))
dc.DrawImage(img, width/4, height/4)
dc.SavePNG("D:\\temp\\out.png")
}
输出样例图片:
添加文字
gg中默认字体为basicfont.Face7x13
,可修改为合适的字体与大小。
设定24号字体,且默认颜色为蓝色:
import (
"github.com/fogleman/gg"
"github.com/golang/freetype/truetype"
"golang.org/x/image/font/gofont/goregular"
)
func initTextContext(w, h int) *gg.Context {
dc := gg.NewContext(w, h)
dc.DrawRectangle(0, 0, float64(w), float64(h))
dc.SetRGB255(245, 245, 245)
dc.Fill()
dc.SetRGB255(0, 0, 255)
font, err := truetype.Parse(goregular.TTF)
if err != nil {
log.Fatal(err)
}
face := truetype.NewFace(font, &truetype.Options{Size: 24})
dc.SetFontFace(face)
return dc
}
绘制文本
通过DrawString(s string, x, y float64)
可方便地绘制文本:
- 默认为左对齐;
- 坐标点(x,y)为文本框的左下角位置:所以在左上角上绘制时坐标为(0, dc.FontHeight())
通过MeasureString
可计算要绘制文本的宽、高。
func drawText() {
width := 300
height := 300
dc := gg.NewContext(width, height)
dc.DrawRectangle(0, 0, float64(width), float64(width))
dc.SetRGB255(245, 245, 245)
dc.Fill()
dc.SetRGB255(0, 0, 255)
pos := dc.FontHeight()
text := "Hello World!"
dc.DrawString(text, 0, pos)
// set font
font, err := truetype.Parse(goregular.TTF)
if err != nil {
log.Fatal(err)
}
face := truetype.NewFace(font, &truetype.Options{Size: 24})
dc.SetFontFace(face)
pos += 50
dc.DrawString(text, 10, pos)
// center align
sW, sH := dc.MeasureString(text)
dc.DrawString(text, (float64(width)-sW)/2, (float64(height)-sH)/2)
// bottom
//dc.DrawString(text, 0, float64(height)-dc.FontHeight())
dc.DrawString(text, 0, float64(height))
dc.SavePNG("D:\\temp\\text.png")
}
样例显示:
文本对齐
通过计算,可设定文本的对齐方式,但太麻烦;可通过DrawStringAnchored(s string, x, y, ax, ay float64)
设定文本的对齐方式(w、h为文本的宽、高):
- 具体显示位置为:(x-wax, y+hay);
- 左对齐为:(0, dc.FontHeight(), 0, 0)
- 居中对齐为:(float64(width/2), float64(height/2), 0.5, 0.5)
- 右对齐为:(float64(width), float64(height), 1, 0)
func drawAlignText() {
width := 300
height := 300
dc := initTextContext(width, height)
text := "Hello World!"
dc.DrawStringAnchored(text, 0, dc.FontHeight(), 0, 0)
// center
dc.DrawStringAnchored(text, float64(width/2), float64(height/2), 0.5, 0.5)
// right
//dc.DrawStringAnchored(text, float64(width), float64(height)-dc.FontHeight(), 1, 1)
dc.DrawStringAnchored(text, float64(width), float64(height), 1, 0)
dc.SavePNG("D:\\temp\\text.png")
}
样例显示:
多行显示
当文本内容过长时,需要通过DrawStringWrapped(s string, x, y, ax, ay, width, lineSpacing float64, align Align)
折行显示:
- width为显示文本框的宽度;
- lineSpacing为行高:多少个字体高度,实际行高为
lineSpacing*dc.FontHeight()
; - align为文本在文本框中对齐方式
func drawMultiLine() {
width := 300
height := 300
dc := initTextContext(width, height)
text := "Hello World! Hello World2! Hello World3!"
dc.DrawStringWrapped(text, float64(width/2), float64(height/2), 0.5, 0.5,
float64(width/2), 1.5, gg.AlignLeft)
dc.SavePNG("D:\\temp\\text.png")
}
样例显示: