对于公网应用来说,海报功能是非常非常重要的,它不仅能扩大应用的知名度,还能起到营销的作用。
那从技术上讲,海报该怎么去做呢?
大部分应用都会选择前端渲染,比如 canvas 渲染,这是最常见的。当然也有一些应用选择后端生成,两者各有各的好处,也有各自的缺点。
这里我们不去讨论前端怎么渲染,只讲后端怎么生成。
先来看下我们将要完成的效果,这是没有加文字和二维码的图片:
这是加上我们的二维码和汉字的样子:
因为微信不允许使用二维码的图片,所以我给打马赛克了。
一、使用 Go 的 image 库
Go 在处理图形上,并没有其他语言,比如 Java、Python 这些库多。
主要 Go 的发力点并不在上面,但是如果工作需要用到 Go 来做海报,也不是不可以。
首先我们可以看到 Go 的官方包里面也提供了 image 包,可以用它来处理图片。
但是,并不是特别建议新手小白直接用这包来处理图片,那你会触碰到很多底层的知识。
更加建议使用一些别人封装好的库,使用起来会更加便捷顺手。所以这里我们就直接跳过了哈,嘻嘻。
二、github.com/golang/freetype
这里我推荐的第一个库是这个,他相对来说封装得不是那么面目全非,所以代码量会多一些,但是他比较能熟悉 image 标准库的使用。
1、安装
这个库的官网地址:https://github.com/golang/freetype
安装命令:
代码语言:javascript复制go get github.com/golang/freetype
然后就可以直接使用了,不过后面我们会用到重置图片大小,需要用到另外一个库,这里也可以一起安装下:
代码语言:javascript复制go get github.com/nfnt/resize
2、准备材料
在实际开发中,你需要向设计师要两张图,一张是最后的效果图,一张是把需要生成的素材抠掉了的图。
如果里面涉及到自定义字体的还得让他们提供字体给我们。
3、上代码
首先来看下我的文件结构:
- cmd 是我的入口文件
- public 素材这些我都放这下面了
- fonts 这里面我放的是我们的自定义字体
- image 这里我放了两张图,bg.png 背景图 和 wx.png 二维码图。
接下来直接上代码:
代码语言:javascript复制package main
import (
"bytes"
"fmt"
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
"github.com/nfnt/resize"
"image"
"image/draw"
"image/jpeg"
"image/png"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
)
var (
fontKai *truetype.Font // 字体
)
func main() {
// 根据路径打开模板文件
templateFile, err := os.Open("./public/image/bg.png")
if err != nil {
panic(err)
}
defer templateFile.Close()
// 解码
templateFileImage, err := png.Decode(templateFile)
if err != nil {
panic(err)
}
// 新建一张和模板文件一样大小的画布
newTemplateImage := image.NewRGBA(templateFileImage.Bounds())
// 将模板图片画到新建的画布上
draw.Draw(newTemplateImage, templateFileImage.Bounds(), templateFileImage, templateFileImage.Bounds().Min, draw.Over)
// 加载字体文件 这里我们加载两种字体文件
fontKai, err = loadFont("./public/fonts/kz_wyzt.ttf")
if err != nil {
log.Panicln(err.Error())
return
}
// 向图片中写入文字
// 在写入之前有一些准备工作
content := freetype.NewContext()
content.SetClip(newTemplateImage.Bounds())
content.SetDst(newTemplateImage)
// 设置字体颜色
content.SetSrc(image.White)
// 设置字体分辨率
content.SetDPI(72)
// 设置字体大小
content.SetFontSize(40)
// 设置字体样式,就是我们上面加载的字体
content.SetFont(fontKai)
// 正式写入文字
// 参数1:要写入的文字
// 参数2:文字坐标
content.DrawString("电话:13800008888 地址:xxx_xxx_xxx 商铺", freetype.Pt(151, 2100))
// 读取二维码图片
erCodeTemplateFile, err := os.Open("./public/image/wx.png")
if err != nil {
panic(err)
}
defer templateFile.Close()
// 解码
imageData, err := png.Decode(erCodeTemplateFile)
if err != nil {
panic(err)
}
// 2、重新调整要粘贴图片尺寸
imageData = resize.Resize(300, 300, imageData, resize.Lanczos3)
// 粘贴缩略图
draw.Draw(newTemplateImage,
newTemplateImage.Bounds().Add(image.Pt(1200, 1800)),
imageData,
imageData.Bounds().Min,
draw.Over)
// 保存图片
saveFile(newTemplateImage)
}
// 根据路径加载字体文件
// path 字体的路径
func loadFont(path string) (font *truetype.Font, err error) {
var fontBytes []byte
fontBytes, err = ioutil.ReadFile(path) // 读取字体文件
if err != nil {
err = fmt.Errorf("加载字体文件出错:%s", err.Error())
return
}
font, err = freetype.ParseFont(fontBytes) // 解析字体文件
if err != nil {
err = fmt.Errorf("解析字体文件出错,%s", err.Error())
return
}
return
}
func saveFile(pic *image.RGBA) {
dstFile, err := os.Create("./public/out/1.png")
if err != nil {
fmt.Println(err)
}
defer dstFile.Close()
png.Encode(dstFile, pic)
}
大部分代码都写了注释,如果你有疑问欢迎加我们交流群提问。
三、github.com/hitailang/poster
咱们做事绝对不能一杆子捅到底,还得再准备一个备用库。
前不久一个同学,发消息问我,有个库不能用了,库的 GitHub 都打不开了,问我怎么办?
我能怎么办?这显然是作者关库了呗,不想开放了哇,遇到这种要么找别人下好的给你,要么就直接换库呗,找个替代库。
这个库在封装上比较便利,使用起来比上一个库更加便捷。
1、安装
官方地址:https://github.com/hitailang/poster
安装命令:
代码语言:javascript复制go get github.com/hitailang/poster
2、上代码
这个库的官方 GitHub 首页就有详细的 demo,我这里就直接抄过来了:
代码语言:javascript复制
func main(){
nullHandler := &handler.NullHandler{}
ctx := &handler.Context{
//图片都绘在这个PNG载体上
PngCarrier: core.NewPNG(0, 0, 750, 1334),
}
//绘制背景图
backgroundHandler := &handler.BackgroundHandler{
X: 0, // 图片x坐标
Y: 0, // 图片y坐标
Path: "./assets/background.png", //图片路径
}
//绘制圆形图像
imageCircleLocalHandler := &handler.ImageCircleLocalHandler{
X: 30, // 图片x坐标
Y: 50, // 图片y坐标
Path: "./assets/reward.png",
//URL: "http://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTLJT9ncWLPov6rAzn4VCPSC4QoAvdangHRB1JgszqCvffggAysvzpm5MDb72Io4g9YAScHEw7xSWg/132", //图片路径
}
//绘制本地图像
imageLocalHandler := &handler.ImageLocalHandler{
X: 30, // 图片x坐标
Y: 400,// 图片y坐标
Path: "./assets/reward.png", //图片路径
}
//绘制二维码
qrCodeHandler := &handler.QRCodeHandler{
X: 30, // 二维码x坐标
Y: 860,// 二维码y坐标
URL: "https://github.com/hitailang/poster", // 二维码跳转URL地址
}
//绘制文字
textHandler1 := &handler.TextHandler{
Next: handler.Next{},
X: 180, // 文字x坐标
Y: 105, // 文字y坐标
Size: 20, // 文字大小
R: 255, // 文字颜色RGB值
G: 241,
B: 250,
Text: "如果觉得这个库对您有用", // 文字内容
FontPath: "./assets/msyh.ttf", // 字体文件
}
//绘制文字
textHandler2 := &handler.TextHandler{
Next: handler.Next{},
X: 180,
Y: 150,
Size: 22,
R: 255,
G: 241,
B: 250,
Text: "请随意赞赏~~",
FontPath: "./assets/msyh.ttf",
}
//结束绘制,把前面的内容合并成一张图片,输出到build目录
endHandler := &handler.EndHandler{
Output: "./build/poster_" xid.New().String() ".png",
}
// 链式调用绘制过程
nullHandler.
SetNext(backgroundHandler).
SetNext(imageCircleLocalHandler).
SetNext(textHandler1).
SetNext(textHandler2).
SetNext(imageLocalHandler).
SetNext(qrCodeHandler).
SetNext(endHandler)
// 开始执行业务
if err := nullHandler.Run(ctx); err != nil {
// 异常
fmt.Println("Fail | Error:" err.Error())
return
}
// 成功
fmt.Println("Success")
return
}
这个库,有两个缺点:
- 官方的 handler 在绘制图片时,不支持缩放大小,所以如果有需要调整大小的,要么自己实现它的 handler,要么调整图片大小
- 官方的库在绘制圆形头像时,只支持绘制 png 格式的,否则在剪切时会出现噪点
四、总结
一般开发中很少会让后台绘制海报,毕竟现在大部分海报上都会带有分享者,也就是用户的头像信息啥的,属于专属图片。
所以这种场景大部分都是提供模板给前端绘制。
但是有些场景,比如商品信息,活动信息等,这些统一的一样的,还是有在后端生成的。
你学废了么?