37DATA

Go-合成周年祝福图片

需求

(1)生日周年系统,需要根据员工的信息,合成周年祝福图片

(2)生成邮件内容进行发送

(3)不同周年使用图片,元素不同

实现效果:如2周年

Image

  任务拆解

(1)周年祝福图片上面有动态合成的元素有:

第x周年的底图、员工姓名、周年数字、入职日期、员工工号以及信息所用到的一些字体

(2)步骤:

A.读取底图进行解码

B.新建一张有边界的画布

C.将模板底图画到画布上

D.姓名等其他信息涉及字体和坐标,所以得提前加载字体

E.接着设置姓名坐标,字体,大小,内容并画到画布上

F.周年数字,入职日期,员工工号同上

H.生成一张新的图片

G.将图片设置成邮件的内容发送到相应的人

(3) 定时任务每天都检测周年的员工,定时操作(2)步骤

新图片生成,看代码实现

bir_notice.go

...//创建一个图片工具对象imageItem := new(library.ImageItem)//掉图片工具类方法,传入姓名,工号,周年,入职日期,生成周年图片并返回路径filePath, fileName, err := imageItem.MakeYearsImage(staffName, workId, empYears, date)...
封装好的图片工具类,包含1到14周年的
image_helper.go
package libraryimport (   "bytes"   "encoding/base64"   "errors"   "fmt"   "github.com/gogf/gf/errors/gerror"   "github.com/gogf/gf/frame/g"   "github.com/gogf/gf/util/gconv"   "github.com/golang/freetype"   "github.com/golang/freetype/truetype"   "image"   "image/draw"   "image/jpeg"   "image/png"   "io"   "io/ioutil"   "net/http"   "os"   "time")var (   fontWryh   *truetype.Font // 名字字体   fontSyhx   *truetype.Font // 日期字体   fontMsyhbd *truetype.Font // 工号字体)type ImageItem struct {   FontSize   int    //字体大小   NameX      int    //姓名X坐标   NameY      int    //姓名Y坐标   DateX      int    //日期Y坐标   DateY      int    //日期Y坐标   UserIdX    int    //工号X坐标   UserIdY    int    //工号Y坐标   ImgPath    string //模板文件文件路径   ImgPathNew string //生成新文件路径文件路径   NewImg     string //新图片   Title      string //标题}//MakeYearsImage 生成周年图片func (i *ImageItem) MakeYearsImage(staffName string, workId int, empYears int, date string) (filePath string, fileName string, err error) {   filePath = ""   filePath = g.Cfg().GetString("birImages.dir")   imageItem, err := i.setImageItem(empYears, filePath)   g.Dump("imageItem:", imageItem)   if err != nil {      return "", "", err   }   // 根据路径打开模板文件   templateFile, err := os.Open(imageItem.ImgPath)   if err != nil {      return "", "", err   }   defer templateFile.Close()   // 解码   templateFileImage, err := jpeg.Decode(templateFile)   if err != nil {      return "", "", err   }   // 新建一张和模板文件一样大小的画布   newTemplateImage := image.NewRGBA(templateFileImage.Bounds())   // 将模板图片画到新建的画布上   draw.Draw(newTemplateImage, templateFileImage.Bounds(), templateFileImage, templateFileImage.Bounds().Min, draw.Over)   // 加载字体文件  这里我们加载两种字体文件   fontWryh, err = i.loadFont(filePath + "wryh.ttf")   if err != nil {      return "", "", err   }   fontSyhx, err = i.loadFont(filePath + "syhx.ttf")   if err != nil {      return "", "", err   }   fontMsyhbd, err = i.loadFont(filePath + "msyhbd.ttf")   if err != nil {      return "", "", err   }   // 向图片中写入文字   // 在写入之前有一些准备工作   content := freetype.NewContext()   content.SetClip(newTemplateImage.Bounds())   content.SetDst(newTemplateImage)   content.SetSrc(image.Black) // 设置字体颜色   content.SetDPI(72)          // 设置字体分辨率   //     正式写入姓名   // 参数1:要写入的文字   // 参数2:文字坐标   content.SetSrc(image.Black)   content.SetFont(fontMsyhbd)   content.SetFontSize(gconv.Float64(20)) // 设置字体大小   _, err = content.DrawString("NO."+gconv.String(workId), freetype.Pt(imageItem.UserIdX, imageItem.UserIdY))   if err != nil {      return "", "", err   }   content.SetSrc(image.Black)   content.SetFont(fontWryh)                              // 设置字体样式,就是我们上面加载的字体   content.SetFontSize(gconv.Float64(imageItem.FontSize)) // 设置字体大小   _, err = content.DrawString(staffName, freetype.Pt(imageItem.NameX, imageItem.NameY))   if err != nil {      fmt.Println("err:", err)      return "", "", err   }   content.SetFont(fontSyhx)   content.SetFontSize(gconv.Float64(imageItem.FontSize)) // 设置字体大小   _, err = content.DrawString(date, freetype.Pt(imageItem.DateX, imageItem.DateY))   if err != nil {      return "", "", err   }   // 保存图片  ---> 在此我们统一将文件保存到:i.ImgPathNew   err = i.saveFile(newTemplateImage, imageItem.ImgPathNew)   if err != nil {      return "", "", err   }   return imageItem.ImgPathNew, imageItem.NewImg, nil}func (i *ImageItem) setImageItem(empYears int, filePath string) (imageItem ImageItem, err error) {   TitleFormat := "相识在37,%v周年纪念日快乐!"   switch empYears {   case 1:      imageItem.FontSize = 28      imageItem.NameX = 172      imageItem.NameY = 183      imageItem.DateX = 315      imageItem.DateY = 790      imageItem.UserIdX = 59      imageItem.UserIdY = 850      imageItem.ImgPath = filePath + "oneyear2020.jpg"      imageItem.ImgPathNew = filePath + "oneyear2020_new.jpg"      imageItem.NewImg = "oneyear2020_new.jpg"      imageItem.Title = fmt.Sprintf(TitleFormat, "一")   case 2:      imageItem.FontSize = 28      imageItem.NameX = 158      imageItem.NameY = 173      imageItem.DateX = 310      imageItem.DateY = 684      imageItem.UserIdX = 59      imageItem.UserIdY = 850      imageItem.ImgPath = filePath + "twoyear2020.jpg"      imageItem.ImgPathNew = filePath + "twoyear2020_new.jpg"      imageItem.NewImg = "twoyear2020_new.jpg"      imageItem.Title = fmt.Sprintf(TitleFormat, "二")   case 3:      imageItem.FontSize = 28      imageItem.NameX = 165      imageItem.NameY = 189      imageItem.DateX = 318      imageItem.DateY = 785      imageItem.UserIdX = 59      imageItem.UserIdY = 850      imageItem.ImgPath = filePath + "threeyear2020.jpg"      imageItem.ImgPathNew = filePath + "threeyear2020_new.jpg"      imageItem.NewImg = "threeyear2020_new.jpg"      imageItem.Title = fmt.Sprintf(TitleFormat, "三")   case 4:      imageItem.FontSize = 28      imageItem.NameX = 160      imageItem.NameY = 173      imageItem.DateX = 315      imageItem.DateY = 638      imageItem.UserIdX = 59      imageItem.UserIdY = 850      imageItem.ImgPath = filePath + "fouryear2020.jpg"      imageItem.ImgPathNew = filePath + "fouryear2020_new.jpg"      imageItem.NewImg = "fouryear2020_new.jpg"      imageItem.Title = fmt.Sprintf(TitleFormat, "四")   case 5:      imageItem.FontSize = 28      imageItem.NameX = 165      imageItem.NameY = 182      imageItem.DateX = 315      imageItem.DateY = 775      imageItem.UserIdX = 62      imageItem.UserIdY = 850      imageItem.ImgPath = filePath + "fiveyear2020.jpg"      imageItem.ImgPathNew = filePath + "fiveyear2020_new.jpg"      imageItem.NewImg = "fiveyear2020_new.jpg"      imageItem.Title = fmt.Sprintf(TitleFormat, "五")   case 6:      imageItem.FontSize = 28      imageItem.NameX = 157      imageItem.NameY = 174      imageItem.DateX = 315      imageItem.DateY = 682      imageItem.UserIdX = 55      imageItem.UserIdY = 850      imageItem.ImgPath = filePath + "sixyear2020.jpg"      imageItem.ImgPathNew = filePath + "sixyear2020_new.jpg"      imageItem.NewImg = "sixyear2020_new.jpg"      imageItem.Title = fmt.Sprintf(TitleFormat, "六")   case 7:      imageItem.FontSize = 28      imageItem.NameX = 165      imageItem.NameY = 160      imageItem.DateX = 315      imageItem.DateY = 812      imageItem.UserIdX = 55      imageItem.UserIdY = 850      imageItem.ImgPath = filePath + "sevenyear2020.jpg"      imageItem.ImgPathNew = filePath + "sevenyear2020_new.jpg"      imageItem.NewImg = "sevenyear2020_new.jpg"      imageItem.Title = fmt.Sprintf(TitleFormat, "七")   case 8:      imageItem.FontSize = 28      imageItem.NameX = 157      imageItem.NameY = 173      imageItem.DateX = 315      imageItem.DateY = 596      imageItem.UserIdX = 55      imageItem.UserIdY = 850      imageItem.ImgPath = filePath + "eightyear2020.jpg"      imageItem.ImgPathNew = filePath + "eightyear2020_new.jpg"      imageItem.NewImg = "eightyear2020_new.jpg"      imageItem.Title = fmt.Sprintf(TitleFormat, "八")   case 9:      imageItem.FontSize = 28      imageItem.NameX = 157      imageItem.NameY = 173      imageItem.DateX = 315      imageItem.DateY = 723      imageItem.UserIdX = 57      imageItem.UserIdY = 850      imageItem.ImgPath = filePath + "nineyear2020.jpg"      imageItem.ImgPathNew = filePath + "nineyear2020_new.jpg"      imageItem.NewImg = "nineyear2020_new.jpg"      imageItem.Title = fmt.Sprintf(TitleFormat, "九")   case 10:      imageItem.FontSize = 28      imageItem.NameX = 168      imageItem.NameY = 167      imageItem.DateX = 315      imageItem.DateY = 818      imageItem.UserIdX = 62      imageItem.UserIdY = 850      imageItem.ImgPath = filePath + "tenyear2020.jpg"      imageItem.ImgPathNew = filePath + "tenyear2020_new.jpg"      imageItem.NewImg = "tenyear2020_new.jpg"      imageItem.Title = fmt.Sprintf(TitleFormat, "十")   case 11:      imageItem.FontSize = 28      imageItem.NameX = 157      imageItem.NameY = 173      imageItem.DateX = 315      imageItem.DateY = 681      imageItem.UserIdX = 57      imageItem.UserIdY = 850      imageItem.ImgPath = filePath + "elevenyear2020.jpg"      imageItem.ImgPathNew = filePath + "elevenyear2020_new.jpg"      imageItem.NewImg = "elevenyear2020_new.jpg"      imageItem.Title = fmt.Sprintf(TitleFormat, "十一")   case 12:      imageItem.FontSize = 28      imageItem.NameX = 157      imageItem.NameY = 173      imageItem.DateX = 315      imageItem.DateY = 765      imageItem.UserIdX = 57      imageItem.UserIdY = 850      imageItem.ImgPath = filePath + "twelveyear2020.jpg"      imageItem.ImgPathNew = filePath + "twelveyear2020_new.jpg"      imageItem.NewImg = "twelveyear2020_new.jpg"      imageItem.Title = fmt.Sprintf(TitleFormat, "十二")   case 13:      imageItem.FontSize = 40      imageItem.NameX = 223      imageItem.NameY = 244      imageItem.DateX = 450      imageItem.DateY = 990      imageItem.UserIdX = 100      imageItem.UserIdY = 1200      imageItem.ImgPath = filePath + "year13.jpg"      imageItem.ImgPathNew = filePath + "year13_new.jpg"      imageItem.NewImg = "year13_new.jpg"      imageItem.Title = fmt.Sprintf(TitleFormat, "十三")   case 14:      imageItem.FontSize = 40      imageItem.NameX = 223      imageItem.NameY = 244      imageItem.DateX = 450      imageItem.DateY = 1080      imageItem.UserIdX = 100      imageItem.UserIdY = 1200      imageItem.ImgPath = filePath + "year14.jpg"      imageItem.ImgPathNew = filePath + "year14_new.jpg"      imageItem.NewImg = "year14_new.jpg"      imageItem.Title = fmt.Sprintf(TitleFormat, "十四")   case 15:      imageItem.FontSize = 35      imageItem.NameX = 223      imageItem.NameY = 244      imageItem.DateX = 485      imageItem.DateY = 1079      imageItem.UserIdX = 100      imageItem.UserIdY = 1200      imageItem.ImgPath = filePath + "year15.jpg"      imageItem.ImgPathNew = filePath + "year15_new.jpg"      imageItem.NewImg = "year15_new.jpg"      imageItem.Title = fmt.Sprintf(TitleFormat, "十五")   }   return imageItem, nil}// 根据路径加载字体文件// path 字体的路径func (i *ImageItem) 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 (i *ImageItem) saveFile(pic *image.RGBA, filePath string) error {   dstFile, err := os.Create(filePath)   if err != nil {      return err   }   defer dstFile.Close()   err = png.Encode(dstFile, pic)   if err != nil {      return err   }   return nil}//GetLocImgBase64 转换本地图片,path图片路径 ,isCompress:是否压缩func GetLocImgBase64(path string, isCompress bool) (baseImg string, err error) {   //获取本地文件   file, err := os.Open(path)   if err != nil {      err = errors.New("获取本地图片失败")      return   }   defer file.Close()   imgByte, _ := ioutil.ReadAll(file)   //压缩图片   if isCompress {      imgByte = compressImageResource(imgByte)   }   // 判断文件类型,生成一个前缀,拼接base64后可以直接粘贴到浏览器打开,不需要可以不用下面代码   //取图片类型   mimeType := http.DetectContentType(imgByte)   switch mimeType {   case "image/jpeg":      baseImg = "data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(imgByte)   case "image/png":      baseImg = "data:image/png;base64," + base64.StdEncoding.EncodeToString(imgByte)   }   return}//GetUrlImgBase64 获取网络图片func GetUrlImgBase64(path string) (baseImg string, err error) {   //获取网络图片   client := &http.Client{      Timeout: time.Second * 5, //超时时间   }   var bodyImg io.Reader   request, err := http.NewRequest("GET", path, bodyImg)   if err != nil {      err = errors.New("获取网络图片失败")      return   }   respImg, _ := client.Do(request)   defer respImg.Body.Close()   imgByte, _ := ioutil.ReadAll(respImg.Body)   // 判断文件类型,生成一个前缀,拼接base64后可以直接粘贴到浏览器打开,不需要可以不用下面代码   //取图片类型   mimeType := http.DetectContentType(imgByte)   switch mimeType {   case "image/jpeg":      baseImg = "data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(imgByte)   case "image/png":      baseImg = "data:image/png;base64," + base64.StdEncoding.EncodeToString(imgByte)   }   return}func compressImageResource(data []byte) []byte {   img, _, err := image.Decode(bytes.NewReader(data))   if err != nil {      return data   }   buf := bytes.Buffer{}   err = jpeg.Encode(&buf, img, &jpeg.Options{Quality: 40})   if err != nil {      return data   }   if buf.Len() > len(data) {      return data   }   return buf.Bytes()}// ClipImageResource 图片调整func ClipImageResource(data []byte, quality int) (out []byte, err error) {   img, fm, err := image.Decode(bytes.NewReader(data))   if err != nil {      err = gerror.Wrap(err, "image decode失败")      return   }   buf := bytes.Buffer{}   switch fm {   case "jpeg":      if quality != 0 {         //压缩         err = jpeg.Encode(&buf, img, &jpeg.Options{Quality: quality})         if err != nil {            err = gerror.Wrap(err, "jpeg encode失败")            return         }         if buf.Len() > len(data) {            out = data         } else {            out = buf.Bytes()         }      }   }   if len(out) == 0 {      out = data   }   return}中间需要做一些业务告警处理,比如生成周年图片失败,给开发发小七告警filePath, fileName, err := imageItem.MakeYearsImage(staffName, workId, empYears, date)if err != nil {   fmt.Println("err:", err)   //合成集团周年图片失败,给开发发告警信息   warringNotice := new(birNotice)   warringNotice.Type = 5   warringNotice.TaskType = 1   warringNotice.SendTitle = "合成集团周年图片失败告警"   warringNotice.SendContent = "合成集团周年图片失败信息:filePath:" + filePath + ",fileName:" + fileName + ",err:" + gconv.String(err)   warringNotice.ReceiveList = g.Config().GetString("birthday.workIds.dev")   warringNotice.SendTime = gtime.Datetime()   _ = warringNotice.sendMsg("")}

将图片设置成邮件内容发送

bir_notice.go
//需做部分名单拦截...//发送邮件err = library.SendMail(mailTo, "37点赞", c.SendTitle, c.SendContent, c.PicUrl, c.PicUrl)//当然,邮件发送失败也需要实时给开发告警if err != nil {   fmt.Println("sendYearEmailMsg err:", err)   //发送邮件失败,给开发发告警信息   warringNotice := new(birNotice)   warringNotice.Type = 5   warringNotice.TaskType = 1   warringNotice.SendTitle = "发送集团周年邮件消息失败告警"   warringNotice.SendContent = "发送集团周年邮件数据:接收工号:(" + c.ReceiveList + ")原始数据:SendMail(" + gconv.String(mailTo) + ",37点赞," + c.SendTitle + "," + c.SendContent + "," + c.PicUrl + "," + c.PicUrl + ")\n" + "err:" + gconv.String(err)   warringNotice.ReceiveList = g.Config().GetString("birthday.workIds.dev")   warringNotice.SendTime = gtime.Datetime()   _ = warringNotice.sendMsg("")}

邮件工具类

gmail_helper.go
///SendMail 发送邮件func SendMail(mailTo []string, serverName, subject, body, filePath, fileName string) error {   address := g.Cfg().GetString("birthday.email.account")   password := g.Cfg().GetString("birthday.email.password")   host := g.Cfg().GetString("birthday.email.host")   port := g.Cfg().GetString("birthday.email.port")   mail := gomail.NewMessage()   mail.SetHeader("From", mail.FormatAddress(address, serverName))   mail.SetHeader("To", mailTo...)   mail.SetHeader("Subject", subject)   mail.SetBody("text/html", body)   if len(filePath) > 0 {      mail.Embed(fileName) //设置图片为内容   }   dialer := gomail.NewDialer(host, gconv.Int(port), address, password)   return dialer.DialAndSend(mail)}

后台预览发送记录

邮件发送的内容及图片,后台同步记录,点击查看可以实时预览

Image

以上就是用go实现图片合成的小demo,供大家参考