golang后端动态生成图片、一文搞懂image/draw使用、一张图片粘贴到另一张图片上、根据url获取图片内容、在图片周围画线、invalidJPEGformat: missingSOImarke
at 2年前 ca Golang pv 1378 by touch
后端动态生成图片、在图片上写文字、将另一张图片贴到模板图片上、图片生成边框
最近做项目,遇到一个需求,在后端根据模板文件动态的生成图片然后返回给前端,原以为是一个很简单的需求,但在此过程中遇到了很多的坑,在全网也没有找到一篇合适的博客来讲清楚这件事,特此记录。
首先准备一张模板图片:
既然要写字,肯定要用到字体,字体我们可以从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 }
原本到这里就应该结束的,但是,。。。但是产品经理说粘贴的图片周围要加一圈线框,这样好看,类似这样:
我说那在图片模板里直接加好不行吗,图片模板里加好了我图片直接粘贴到框里面,产品说不行,图片可能是竖向的图,我说那你给我两个模板吧,产品经理说不行,你就简单的画一条线,很容易的。。。
没办法,没能偷到懒,只好肝
其实这简简单单的4条线真不简单,线框到图片之间是透明的,我的想法是这样的:
1、先新建一个透明的图层
2、将图片粘贴到透明图层上
3、在图片四周画上线条
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 }
最终的效果图:
版权声明
本文仅代表作者观点,不代表码农殇立场。
本文系作者授权码农殇发表,未经许可,不得转载。
已有0条评论