golang后端动态生成图片、一文搞懂image/draw使用、一张图片粘贴到另一张图片上、根据url获取图片内容、在图片周围画线、invalidJPEGformat: missingSOImarke
后端动态生成图片、在图片上写文字、将另一张图片贴到模板图片上、图片生成边框
最近做项目,遇到一个需求,在后端根据模板文件动态的生成图片然后返回给前端,原以为是一个很简单的需求,但在此过程中遇到了很多的坑,在全网也没有找到一篇合适的博客来讲清楚这件事,特此记录。
首先准备一张模板图片:
既然要写字,肯定要用到字体,字体我们可以从windows中获取亦可以到网上下载,windows中字体路径为:
C:\Windows\Fonts
1、在图片上写字
代码如下:
package main
import (
"fmt"
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
"image"
"image/color"
"image/draw"
"image/png"
"io/ioutil"
"log"
"os"
)
var (
fontKai *truetype.Font // 字体
fontTtf *truetype.Font // 字体
)
func main() {
// 根据路径打开模板文件
templateFile, err := os.Open("C:\\Users\\yida\\GolandProjects\\GoProjectDemo\\A-go-study\\template.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("C:\\Users\\yida\\GolandProjects\\GoProjectDemo\\A-go-study\\simkai.ttf")
if err != nil {
log.Panicln(err.Error())
return
}
fontTtf, err = loadFont("C:\\Users\\yida\\GolandProjects\\GoProjectDemo\\A-go-study\\STXINWEI.TTF")
if err != nil {
log.Panicln(err.Error())
return
}
// 向图片中写入文字
// 在写入之前有一些准备工作
content := freetype.NewContext()
content.SetClip(newTemplateImage.Bounds())
content.SetDst(newTemplateImage)
content.SetSrc(image.Black) // 设置字体颜色
content.SetDPI(72) // 设置字体分辨率
content.SetFontSize(40) // 设置字体大小
content.SetFont(fontKai) // 设置字体样式,就是我们上面加载的字体
// 正式写入文字
// 参数1:要写入的文字
// 参数2:文字坐标
content.DrawString("yida同志:", freetype.Pt(160, 375))
content.DrawString("您在2022年度中表现突出,忠诚奉献、认真负责,", freetype.Pt(230, 450))
content.DrawString("被评为", freetype.Pt(160, 520))
content.DrawString("特发此证,以资鼓励。", freetype.Pt(645, 520))
// 设置字体大小
content.SetFontSize(48)
// 设置字体颜色
content.SetSrc(image.NewUniform(color.RGBA{
R: 237, G: 39, B: 90, A: 255}))
content.DrawString("“最佳奉献奖”,", freetype.Pt(280, 520))
content.SetFont(fontTtf) // 设置字体样式
// 设置字体大小
content.SetFontSize(32)
// 设置字体颜色
content.SetSrc(image.Black)
content.DrawString("东风战略导弹部队", freetype.Pt(898, 660))
content.DrawString("二零二二年五月", freetype.Pt(898, 726))
// 保存图片 ---> 在此我们统一将文件保存到:C:\Users\yida\GolandProjects\GoProjectDemo\A-go-study\dst.png
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("C:\\Users\\yida\\GolandProjects\\GoProjectDemo\\A-go-study\\dst.png")
if err != nil {
fmt.Println(err)
}
defer dstFile.Close()
png.Encode(dstFile, pic)
}
生成的图片如下:
如何在证书中再粘贴一张图片呢
在做项目的时候有一个需求,是用户传一个图片的地址,这个图片可能是存到七牛云或者阿里云上的,然后要将这个图片的缩略图放到证书上,此时我们应该怎么做呢?
- 根据地址获取到图片的内容 存储在七牛云上的一张图片地址:http://qiniu.yueda.vip/123.png
- 原图片可能很大,直接粘贴到模板上不合适,所以要先伸缩成固定的大小
- 将固定大小的图片粘贴到模板上
完整代码如下:
package main
import (
"bytes"
"fmt"
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
"github.com/nfnt/resize"
"image"
"image/color"
"image/draw"
"image/jpeg"
"image/png"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
)
var (
fontKai *truetype.Font // 字体
fontTtf *truetype.Font // 字体
)
func main() {
// 根据路径打开模板文件
templateFile, err := os.Open("C:\\Users\\yida\\GolandProjects\\GoProjectDemo\\A-go-study\\template.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("C:\\Users\\yida\\GolandProjects\\GoProjectDemo\\A-go-study\\simkai.ttf")
if err != nil {
log.Panicln(err.Error())
return
}
fontTtf, err = loadFont("C:\\Users\\yida\\GolandProjects\\GoProjectDemo\\A-go-study\\STXINWEI.TTF")
if err != nil {
log.Panicln(err.Error())
return
}
// 向图片中写入文字
writeWord2Pic(newTemplateImage)
// ====================向模板中粘贴图片 begin========================
// 1、根据地址获取图片内容
imageData, err := getDataByUrl("http://qiniu.yueda.vip/123.png")
if err != nil {
fmt.Println("根据地址获取图片失败,err:", err.Error())
return
}
// 2、重新调整要粘贴图片尺寸
imageData = resize.Resize(387, 183, imageData, resize.Lanczos3)
// 粘贴缩略图
draw.Draw(newTemplateImage,
newTemplateImage.Bounds().Add(image.Pt(228, 558)),
imageData,
imageData.Bounds().Min,
draw.Over)
// ====================向模板中粘贴图片 结束========================
// 保存图片 ---> 在此我们统一将文件保存到:C:\Users\yida\GolandProjects\GoProjectDemo\A-go-study\dst.png
saveFile(newTemplateImage)
}
func writeWord2Pic(newTemplateImage *image.RGBA) {
// 在写入之前有一些准备工作
content := freetype.NewContext()
content.SetClip(newTemplateImage.Bounds())
content.SetDst(newTemplateImage)
content.SetSrc(image.Black) // 设置字体颜色
content.SetDPI(72) // 设置字体分辨率
content.SetFontSize(40) // 设置字体大小
content.SetFont(fontKai) // 设置字体样式,就是我们上面加载的字体
// 正式写入文字
// 参数1:要写入的文字
// 参数2:文字坐标
content.DrawString("yida同志:", freetype.Pt(160, 375))
content.DrawString("您在2022年度中表现突出,忠诚奉献、认真负责,", freetype.Pt(230, 450))
content.DrawString("被评为", freetype.Pt(160, 520))
content.DrawString("特发此证,以资鼓励。", freetype.Pt(645, 520))
// 设置字体大小
content.SetFontSize(48)
// 设置字体颜色
content.SetSrc(image.NewUniform(color.RGBA{
R: 237, G: 39, B: 90, A: 255}))
content.DrawString("“最佳奉献奖”,", freetype.Pt(280, 520))
content.SetFont(fontTtf) // 设置字体样式
// 设置字体大小
content.SetFontSize(32)
// 设置字体颜色
content.SetSrc(image.Black)
content.DrawString("东风战略导弹部队", freetype.Pt(898, 660))
content.DrawString("二零二二年五月", freetype.Pt(898, 726))
}
// 根据路径加载字体文件
// 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("C:\\Users\\yida\\GolandProjects\\GoProjectDemo\\A-go-study\\dst.png")
if err != nil {
fmt.Println(err)
}
defer dstFile.Close()
png.Encode(dstFile, pic)
}
// 根据地址获取图片内容
func getDataByUrl(url string) (img image.Image, err error) {
res, err := http.Get(url)
if err != nil {
err = fmt.Errorf("[%s]通过url获取数据失败,err:%s", url, err.Error())
return
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(res.Body)
// 读取获取的[]byte数据
data, err := ioutil.ReadAll(res.Body)
if err != nil {
err = fmt.Errorf("读取数据失败,err:%s", err.Error())
return
}
if !strings.HasSuffix(url, ".jpg") &&
!strings.HasSuffix(url, ".jpeg") &&
!strings.HasSuffix(url, ".png") {
err = fmt.Errorf("[%s]不支持的图片类型,暂只支持.jpg、.png文件类型", url)
return
}
// []byte 转 io.Reader
reader := bytes.NewReader(data)
if strings.HasSuffix(url, ".jpg") || strings.HasSuffix(url, ".jpeg") {
// 此处jgeg.decode 有坑,明明是.jpg的图片但 会报 invalid JPEG format: missing SOI marker 错误
// 所以当报错时我们再用 png.decode 试试
img, err = jpeg.Decode(reader)
if err != nil {
fmt.Printf("jpeg.Decode err:%s", err.Error())
reader2 := bytes.NewReader(data)
img, err = png.Decode(reader2)
if err != nil {
err = fmt.Errorf("===>png.Decode err:%s", err.Error())
return
}
}
}
if strings.HasSuffix(url, ".png") {
img, err = png.Decode(reader)
if err != nil {
err = fmt.Errorf("png.Decode err:%s", err.Error())
return
}
}
return
}
图片效果1
原本到这里就应该结束的,但是,。。。但是产品经理说粘贴的图片周围要加一圈线框,这样好看,类似这样:
我说那在图片模板里直接加好不行吗,图片模板里加好了我图片直接粘贴到框里面,产品说不行,图片可能是竖向的图,我说那你给我两个模板吧,产品经理说不行,你就简单的画一条线,很容易的。。。
没办法,没能偷到懒,只好肝
其实这简简单单的4条线真不简单,线框到图片之间是透明的,我的想法是这样的:
- 先新建一个透明的图层
- 将图片粘贴到透明图层上
- 在图片四周画上线条
- 将画好线的图片粘贴到模板图片上
package main
import (
"bytes"
"fmt"
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
"github.com/llgcode/draw2d/draw2dimg"
"github.com/nfnt/resize"
"image"
"image/color"
"image/draw"
"image/jpeg"
"image/png"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
)
var (
fontKai *truetype.Font // 字体
fontTtf *truetype.Font // 字体
)
func main() {
// 根据路径打开模板文件
templateFile, err := os.Open("C:\\Users\\yida\\GolandProjects\\GoProjectDemo\\A-go-study\\template.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("C:\\Users\\yida\\GolandProjects\\GoProjectDemo\\A-go-study\\simkai.ttf")
if err != nil {
log.Panicln(err.Error())
return
}
fontTtf, err = loadFont("C:\\Users\\yida\\GolandProjects\\GoProjectDemo\\A-go-study\\STXINWEI.TTF")
if err != nil {
log.Panicln(err.Error())
return
}
// 向图片中写入文字
writeWord2Pic(newTemplateImage)
// ====================向模板中粘贴图片 begin========================
// 1、根据地址获取图片内容
imageData, err := getDataByUrl("http://qiniu.yueda.vip/123.png")
if err != nil {
fmt.Println("根据地址获取图片失败,err:", err.Error())
return
}
// 图片到边框距离
pic2FramePadding := 20
// 获取全景图原始的尺寸
dx := imageData.Bounds().Dx()
dy := imageData.Bounds().Dy()
// 2、重新调整要粘贴图片尺寸
if dx > dy {
// 判断是横图还是竖图
imageData = resize.Resize(uint(387-pic2FramePadding), uint(183-pic2FramePadding), imageData, resize.Lanczos3)
} else {
imageData = resize.Resize(uint(387/2-pic2FramePadding), uint(183-pic2FramePadding), imageData, resize.Lanczos3)
}
// 新建一个透明图层
transparentImg := image.NewRGBA(image.Rect(0, 0, imageData.Bounds().Dx()+pic2FramePadding, imageData.Bounds().Dy()+pic2FramePadding))
// 将缩略图放到透明图层上
draw.Draw(transparentImg,
image.Rect(pic2FramePadding/2, pic2FramePadding/2, transparentImg.Bounds().Dx(), transparentImg.Bounds().Dy()),
imageData,
image.Point{
},
draw.Over)
// 图片周围画线
lineToPic(transparentImg)
// 粘贴缩略图
draw.Draw(newTemplateImage,
transparentImg.Bounds().Add(image.Pt(228, 558)),
transparentImg,
transparentImg.Bounds().Min,
draw.Over)
// ====================向模板中粘贴图片 结束========================
// 保存图片 ---> 在此我们统一将文件保存到:C:\Users\yida\GolandProjects\GoProjectDemo\A-go-study\dst.png
saveFile(newTemplateImage)
}
func lineToPic(transparentImg *image.RGBA) {
gc := draw2dimg.NewGraphicContext(transparentImg)
gc.SetStrokeColor(color.RGBA{
// 线框颜色
R: uint8(36),
G: uint8(106),
B: uint8(96),
A: 0xff})
gc.SetFillColor(color.RGBA{
})
gc.SetLineWidth(5) // 线框宽度
gc.BeginPath()
gc.MoveTo(0, 0)
gc.LineTo(float64(transparentImg.Bounds().Dx()), 0)
gc.LineTo(float64(transparentImg.Bounds().Dx()), float64(transparentImg.Bounds().Dy()))
gc.LineTo(0, float64(transparentImg.Bounds().Dy()))
gc.LineTo(0, 0)
gc.Close()
gc.FillStroke()
}
func writeWord2Pic(newTemplateImage *image.RGBA) {
// 在写入之前有一些准备工作
content := freetype.NewContext()
content.SetClip(newTemplateImage.Bounds())
content.SetDst(newTemplateImage)
content.SetSrc(image.Black) // 设置字体颜色
content.SetDPI(72) // 设置字体分辨率
content.SetFontSize(40) // 设置字体大小
content.SetFont(fontKai) // 设置字体样式,就是我们上面加载的字体
// 正式写入文字
// 参数1:要写入的文字
// 参数2:文字坐标
content.DrawString("yida同志:", freetype.Pt(160, 375))
content.DrawString("您在2022年度中表现突出,忠诚奉献、认真负责,", freetype.Pt(230, 450))
content.DrawString("被评为", freetype.Pt(160, 520))
content.DrawString("特发此证,以资鼓励。", freetype.Pt(645, 520))
// 设置字体大小
content.SetFontSize(48)
// 设置字体颜色
content.SetSrc(image.NewUniform(color.RGBA{
R: 237, G: 39, B: 90, A: 255}))
content.DrawString("“最佳奉献奖”,", freetype.Pt(280, 520))
content.SetFont(fontTtf) // 设置字体样式
// 设置字体大小
content.SetFontSize(32)
// 设置字体颜色
content.SetSrc(image.Black)
content.DrawString("东风战略导弹部队", freetype.Pt(898, 660))
content.DrawString("二零二二年五月", freetype.Pt(898, 726))
}
// 根据路径加载字体文件
// 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("C:\\Users\\yida\\GolandProjects\\GoProjectDemo\\A-go-study\\dst.png")
if err != nil {
fmt.Println(err)
}
defer dstFile.Close()
png.Encode(dstFile, pic)
}
// 根据地址获取图片内容
func getDataByUrl(url string) (img image.Image, err error) {
res, err := http.Get(url)
if err != nil {
err = fmt.Errorf("[%s]通过url获取数据失败,err:%s", url, err.Error())
return
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(res.Body)
// 读取获取的[]byte数据
data, err := ioutil.ReadAll(res.Body)
if err != nil {
err = fmt.Errorf("读取数据失败,err:%s", err.Error())
return
}
if !strings.HasSuffix(url, ".jpg") &&
!strings.HasSuffix(url, ".jpeg") &&
!strings.HasSuffix(url, ".png") {
err = fmt.Errorf("[%s]不支持的图片类型,暂只支持.jpg、.png文件类型", url)
return
}
// []byte 转 io.Reader
reader := bytes.NewReader(data)
if strings.HasSuffix(url, ".jpg") || strings.HasSuffix(url, ".jpeg") {
// 此处jgeg.decode 有坑,明明是.jpg的图片但 会报 invalid JPEG format: missing SOI marker 错误
// 所以当报错时我们再用 png.decode 试试
img, err = jpeg.Decode(reader)
if err != nil {
fmt.Printf("jpeg.Decode err:%s", err.Error())
reader2 := bytes.NewReader(data)
img, err = png.Decode(reader2)
if err != nil {
err = fmt.Errorf("===>png.Decode err:%s", err.Error())
return
}
}
}
if strings.HasSuffix(url, ".png") {
img, err = png.Decode(reader)
if err != nil {
err = fmt.Errorf("png.Decode err:%s", err.Error())
return
}
}
return
}
最终的效果图
来源 https://blog.csdn.net/qq_40585384/article/details/124762939
文章评论