golang后端动态生成图片、一文搞懂image/draw使用、一张图片粘贴到另一张图片上、根据url获取图片内容、在图片周围画线、invalidJPEGformat: missingSOImarke

at 2年前  ca Golang  pv 1378  by touch  

后端动态生成图片、在图片上写文字、将另一张图片贴到模板图片上、图片生成边框

最近做项目,遇到一个需求,在后端根据模板文件动态的生成图片然后返回给前端,原以为是一个很简单的需求,但在此过程中遇到了很多的坑,在全网也没有找到一篇合适的博客来讲清楚这件事,特此记录。


首先准备一张模板图片:

golang后端动态生成图片、一文搞懂image/draw使用、一张图片粘贴到另一张图片上、根据url获取图片内容、在图片周围画线、invalidJPEGformat: missingSOImarke Golang 第1张


既然要写字,肯定要用到字体,字体我们可以从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)
}

生成的图片如下:

golang后端动态生成图片、一文搞懂image/draw使用、一张图片粘贴到另一张图片上、根据url获取图片内容、在图片周围画线、invalidJPEGformat: missingSOImarke Golang 第2张

如何在证书中再粘贴一张图片呢

在做项目的时候有一个需求,是用户传一个图片的地址,这个图片可能是存到七牛云或者阿里云上的,然后要将这个图片的缩略图放到证书上,此时我们应该怎么做呢?


根据地址获取到图片的内容 存储在七牛云上的一张图片地址: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
}

golang后端动态生成图片、一文搞懂image/draw使用、一张图片粘贴到另一张图片上、根据url获取图片内容、在图片周围画线、invalidJPEGformat: missingSOImarke Golang 第3张

原本到这里就应该结束的,但是,。。。但是产品经理说粘贴的图片周围要加一圈线框,这样好看,类似这样:

golang后端动态生成图片、一文搞懂image/draw使用、一张图片粘贴到另一张图片上、根据url获取图片内容、在图片周围画线、invalidJPEGformat: missingSOImarke Golang 第4张

我说那在图片模板里直接加好不行吗,图片模板里加好了我图片直接粘贴到框里面,产品说不行,图片可能是竖向的图,我说那你给我两个模板吧,产品经理说不行,你就简单的画一条线,很容易的。。。

没办法,没能偷到懒,只好肝

其实这简简单单的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
}


最终的效果图:

golang后端动态生成图片、一文搞懂image/draw使用、一张图片粘贴到另一张图片上、根据url获取图片内容、在图片周围画线、invalidJPEGformat: missingSOImarke Golang 第5张



版权声明

本文仅代表作者观点,不代表码农殇立场。
本文系作者授权码农殇发表,未经许可,不得转载。

 

扫一扫在手机阅读、分享本文

已有0条评论
您是本站第12290名访客 今日有0篇新文章 当前在线 20 人