|
| 1 | +# Golang Gin实践 连载十六 在图片上绘制文字 |
| 2 | + |
| 3 | + |
| 4 | +本章节是 [连载十五](https://github.com/EDDYCJY/blog/blob/master/golang/gin/2018-07-04-Gin%E5%AE%9E%E8%B7%B5-%E8%BF%9E%E8%BD%BD%E5%8D%81%E4%BA%94-%E7%94%9F%E6%88%90%E4%BA%8C%E7%BB%B4%E7%A0%81-%E5%90%88%E5%B9%B6%E6%B5%B7%E6%8A%A5.md) 的补充内容,建议一同食用 |
| 5 | + |
| 6 | +## 前言 |
| 7 | +主要实现**合并后的海报上绘制文字**的功能(这个需求也是常见的很了),内容比较简单 |
| 8 | + |
| 9 | +## 实现 |
| 10 | + |
| 11 | +这里使用的是 [微软雅黑](https://github.com/EDDYCJY/go-gin-example/blob/master/runtime/fonts/msyhbd.ttc) 的字体,请点击进行下载并**存放到 runtime/fonts 目录**下(字体文件占 16 MB 大小) |
| 12 | + |
| 13 | +### 安装 |
| 14 | + |
| 15 | +``` |
| 16 | +$ go get -u github.com/golang/freetype |
| 17 | +``` |
| 18 | + |
| 19 | +### 绘制文字 |
| 20 | + |
| 21 | +打开 service/article_service/article_poster.go 文件,增加绘制文字的业务逻辑,如下: |
| 22 | + |
| 23 | +``` |
| 24 | +type DrawText struct { |
| 25 | + JPG draw.Image |
| 26 | + Merged *os.File |
| 27 | +
|
| 28 | + Title string |
| 29 | + X0 int |
| 30 | + Y0 int |
| 31 | + Size0 float64 |
| 32 | +
|
| 33 | + SubTitle string |
| 34 | + X1 int |
| 35 | + Y1 int |
| 36 | + Size1 float64 |
| 37 | +} |
| 38 | +
|
| 39 | +func (a *ArticlePosterBg) DrawPoster(d *DrawText, fontName string) error { |
| 40 | + fontSource := setting.AppSetting.RuntimeRootPath + setting.AppSetting.FontSavePath + fontName |
| 41 | + fontSourceBytes, err := ioutil.ReadFile(fontSource) |
| 42 | + if err != nil { |
| 43 | + return err |
| 44 | + } |
| 45 | +
|
| 46 | + trueTypeFont, err := freetype.ParseFont(fontSourceBytes) |
| 47 | + if err != nil { |
| 48 | + return err |
| 49 | + } |
| 50 | +
|
| 51 | + fc := freetype.NewContext() |
| 52 | + fc.SetDPI(72) |
| 53 | + fc.SetFont(trueTypeFont) |
| 54 | + fc.SetFontSize(d.Size0) |
| 55 | + fc.SetClip(d.JPG.Bounds()) |
| 56 | + fc.SetDst(d.JPG) |
| 57 | + fc.SetSrc(image.Black) |
| 58 | +
|
| 59 | + pt := freetype.Pt(d.X0, d.Y0) |
| 60 | + _, err = fc.DrawString(d.Title, pt) |
| 61 | + if err != nil { |
| 62 | + return err |
| 63 | + } |
| 64 | +
|
| 65 | + fc.SetFontSize(d.Size1) |
| 66 | + _, err = fc.DrawString(d.SubTitle, freetype.Pt(d.X1, d.Y1)) |
| 67 | + if err != nil { |
| 68 | + return err |
| 69 | + } |
| 70 | +
|
| 71 | + err = jpeg.Encode(d.Merged, d.JPG, nil) |
| 72 | + if err != nil { |
| 73 | + return err |
| 74 | + } |
| 75 | +
|
| 76 | + return nil |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +这里主要使用了 freetype 包,分别涉及如下细项: |
| 81 | + |
| 82 | +1、freetype.NewContext:创建一个新的 Context,会对其设置一些默认值 |
| 83 | + |
| 84 | +``` |
| 85 | +func NewContext() *Context { |
| 86 | + return &Context{ |
| 87 | + r: raster.NewRasterizer(0, 0), |
| 88 | + fontSize: 12, |
| 89 | + dpi: 72, |
| 90 | + scale: 12 << 6, |
| 91 | + } |
| 92 | +} |
| 93 | +``` |
| 94 | + |
| 95 | +2、fc.SetDPI:设置屏幕每英寸的分辨率 |
| 96 | + |
| 97 | +3、fc.SetFont:设置用于绘制文本的字体 |
| 98 | + |
| 99 | +4、fc.SetFontSize:以磅为单位设置字体大小 |
| 100 | + |
| 101 | +5、fc.SetClip:设置剪裁矩形以进行绘制 |
| 102 | + |
| 103 | +6、fc.SetDst:设置目标图像 |
| 104 | + |
| 105 | +7、fc.SetSrc:设置绘制操作的源图像,通常为 [image.Uniform](https://golang.org/pkg/image/#Uniform) |
| 106 | + |
| 107 | +``` |
| 108 | +var ( |
| 109 | + // Black is an opaque black uniform image. |
| 110 | + Black = NewUniform(color.Black) |
| 111 | + // White is an opaque white uniform image. |
| 112 | + White = NewUniform(color.White) |
| 113 | + // Transparent is a fully transparent uniform image. |
| 114 | + Transparent = NewUniform(color.Transparent) |
| 115 | + // Opaque is a fully opaque uniform image. |
| 116 | + Opaque = NewUniform(color.Opaque) |
| 117 | +) |
| 118 | +``` |
| 119 | + |
| 120 | +8、fc.DrawString:根据 Pt 的坐标值绘制给定的文本内容 |
| 121 | + |
| 122 | +### 业务逻辑 |
| 123 | + |
| 124 | +打开 service/article_service/article_poster.go 方法,在 Generate 方法增加绘制文字的代码逻辑,如下: |
| 125 | + |
| 126 | +``` |
| 127 | +func (a *ArticlePosterBg) Generate() (string, string, error) { |
| 128 | + fullPath := qrcode.GetQrCodeFullPath() |
| 129 | + fileName, path, err := a.Qr.Encode(fullPath) |
| 130 | + if err != nil { |
| 131 | + return "", "", err |
| 132 | + } |
| 133 | +
|
| 134 | + if !a.CheckMergedImage(path) { |
| 135 | + ... |
| 136 | +
|
| 137 | + draw.Draw(jpg, jpg.Bounds(), bgImage, bgImage.Bounds().Min, draw.Over) |
| 138 | + draw.Draw(jpg, jpg.Bounds(), qrImage, qrImage.Bounds().Min.Sub(image.Pt(a.Pt.X, a.Pt.Y)), draw.Over) |
| 139 | +
|
| 140 | + err = a.DrawPoster(&DrawText{ |
| 141 | + JPG: jpg, |
| 142 | + Merged: mergedF, |
| 143 | +
|
| 144 | + Title: "Golang Gin 系列文章", |
| 145 | + X0: 80, |
| 146 | + Y0: 160, |
| 147 | + Size0: 42, |
| 148 | +
|
| 149 | + SubTitle: "---煎鱼", |
| 150 | + X1: 320, |
| 151 | + Y1: 220, |
| 152 | + Size1: 36, |
| 153 | + }, "msyhbd.ttc") |
| 154 | +
|
| 155 | + if err != nil { |
| 156 | + return "", "", err |
| 157 | + } |
| 158 | + } |
| 159 | +
|
| 160 | + return fileName, path, nil |
| 161 | +} |
| 162 | +``` |
| 163 | + |
| 164 | +## 验证 |
| 165 | + |
| 166 | +访问生成文章海报的接口 `$HOST/api/v1/articles/poster/generate?token=$token`,检查其生成结果,如下图 |
| 167 | + |
| 168 | + |
| 169 | + |
| 170 | +## 总结 |
| 171 | + |
| 172 | +在本章节在 [连载十五](https://github.com/EDDYCJY/blog/blob/master/golang/gin/2018-07-04-Gin%E5%AE%9E%E8%B7%B5-%E8%BF%9E%E8%BD%BD%E5%8D%81%E4%BA%94-%E7%94%9F%E6%88%90%E4%BA%8C%E7%BB%B4%E7%A0%81-%E5%90%88%E5%B9%B6%E6%B5%B7%E6%8A%A5.md) 的功能上增加了绘制文字,在实现上并不困难,而这两块需求一般会同时出现,大家可以多加练习,了解里面的逻辑和其他 API 😁 |
| 173 | + |
| 174 | +## 参考 |
| 175 | +### 本系列示例代码 |
| 176 | +- [go-gin-example](https://github.com/EDDYCJY/go-gin-example) |
0 commit comments