golang中实现给gif、png、jpeg图片添加文字水印

论坛 期权论坛 脚本     
niminba   2021-5-26 11:15   1661   0

添加水印示例

添加main文件

“watermark/main.go”

package main
import (
 "fmt"
 "watermark/textwatermark" 
)
func main() {
 SavePath := "./kaf"
 str := textwatermark.FontInfo{18, "努力向上", textwatermark.TopLeft, 20, 20, 255, 255, 0, 255}
 arr := make([]textwatermark.FontInfo, 0)
 arr = append(arr, str)
 str2 := textwatermark.FontInfo{Size: 24, Message: "努力向上,涨工资", Position: textwatermark.TopLeft, Dx: 20, Dy: 40, R: 255, G: 255, B: 0, A: 255}
 arr = append(arr, str2)
 //加水印图片路径
 // fileName := "123123.jpg"
 fileName := "17.gif"
 w := new(textwatermark.Water)
 w.Pattern = "2006/01/02"
 textwatermark.Ttf = "./wrzh.ttf" //字体路径
 err := w.New(SavePath, fileName, arr)
 if err != nil {
  fmt.Println(err)
 }
}

golang添加水印包文件

"watermark/textwatermark.go"

package textwatermark
import (
 "errors"
 "fmt"
 "image"
 "image/color"
 "image/draw"
 "image/gif"
 "image/jpeg"
 "image/png"
 "io/ioutil"
 "math/rand"
 "os"
 "time"
 "github.com/golang/freetype"
)
// 水印的位置
const (
 TopLeft int = iota
 TopRight
 BottomLeft
 BottomRight
 Center
)
//字体路径
var Ttf string
type Water struct {
 Pattern string //增加按时间划分的子目录:默认没有时间划分的子目录
}
func (w *Water) New(SavePath, fileName string, typeface []FontInfo) error {
 var subPath string
 subPath = w.Pattern
 dirs, err := createDir(SavePath, subPath)
 if err != nil {
  return err
 }
 imgfile, _ := os.Open(fileName)
 defer imgfile.Close()
 _, str, err := image.DecodeConfig(imgfile)
 if err != nil {
  return err
 }
 newName := fmt.Sprintf("%s%s.%s", dirs, getRandomString(10), str)
 if str == "gif" {
  err = gifFontWater(fileName, newName, typeface)
 } else {
  err = staticFontWater(fileName, newName, str, typeface)
 }
 return err
}
//gif图片水印
func gifFontWater(file, name string, typeface []FontInfo) (err error) {
 imgfile, _ := os.Open(file)
 defer imgfile.Close()
 var err2 error
 gifimg2, _ := gif.DecodeAll(imgfile)
 gifs := make([]*image.Paletted, 0)
 x0 := 0
 y0 := 0
 yuan := 0
 for k, gifimg := range gifimg2.Image {
  img := image.NewNRGBA(gifimg.Bounds())
  if k == 0 {
   x0 = img.Bounds().Dx()
   y0 = img.Bounds().Dy()
  }
  fmt.Printf("%v, %v\n", img.Bounds().Dx(), img.Bounds().Dy())
  if k == 0 && gifimg2.Image[k+1].Bounds().Dx() > x0 && gifimg2.Image[k+1].Bounds().Dy() > y0 {
   yuan = 1
   break
  }
  if x0 == img.Bounds().Dx() && y0 == img.Bounds().Dy() {
   for y := 0; y < img.Bounds().Dy(); y++ {
    for x := 0; x < img.Bounds().Dx(); x++ {
     img.Set(x, y, gifimg.At(x, y))
    }
   }
   img, err2 = common(img, typeface) //添加文字水印
   if err2 != nil {
    break
   }
   //定义一个新的图片调色板img.Bounds():使用原图的颜色域,gifimg.Palette:使用原图的调色板
   p1 := image.NewPaletted(gifimg.Bounds(), gifimg.Palette)
   //把绘制过文字的图片添加到新的图片调色板上
   draw.Draw(p1, gifimg.Bounds(), img, image.ZP, draw.Src)
   //把添加过文字的新调色板放入调色板slice
   gifs = append(gifs, p1)
  } else {
   gifs = append(gifs, gifimg)
  }
 }
 if yuan == 1 {
  return errors.New("gif: image block is out of bounds")
 } else {
  if err2 != nil {
   return err2
  }
  //保存到新文件中
  newfile, err := os.Create(name)
  if err != nil {
   return err
  }
  defer newfile.Close()
  g1 := &gif.GIF{
   Image:     gifs,
   Delay:     gifimg2.Delay,
   LoopCount: gifimg2.LoopCount,
  }
  err = gif.EncodeAll(newfile, g1)
  return err
 }
}
//png,jpeg图片水印
func staticFontWater(file, name, status string, typeface []FontInfo) (err error) {
 //需要加水印的图片
 imgfile, _ := os.Open(file)
 defer imgfile.Close()
 var staticImg image.Image
 if status == "png" {
  staticImg, _ = png.Decode(imgfile)
 } else {
  staticImg, _ = jpeg.Decode(imgfile)
 }
 img := image.NewNRGBA(staticImg.Bounds())
 for y := 0; y < img.Bounds().Dy(); y++ {
  for x := 0; x < img.Bounds().Dx(); x++ {
   img.Set(x, y, staticImg.At(x, y))
  }
 }
 img, err = common(img, typeface) //添加文字水印
 if err != nil {
  return err
 }
 //保存到新文件中
 newfile, err := os.Create(name)
 if err != nil {
  return err
 }
 defer newfile.Close()
 if status == "png" {
  err = png.Encode(newfile, img)
 } else {
  err = jpeg.Encode(newfile, img, &jpeg.Options{100})
 }
 return err
}
//添加文字水印函数
func common(img *image.NRGBA, typeface []FontInfo) (*image.NRGBA, error) {
 var err2 error
 //拷贝一个字体文件到运行目录
 fontBytes, err := ioutil.ReadFile(Ttf)
 if err != nil {
  err2 = err
  return nil, err2
 }
 font, err := freetype.ParseFont(fontBytes)
 if err != nil {
  err2 = err
  return nil, err2
 }
 errNum := 1
Loop:
 for _, t := range typeface {
  info := t.Message
  f := freetype.NewContext()
  f.SetDPI(108)
  f.SetFont(font)
  f.SetFontSize(t.Size)
  f.SetClip(img.Bounds())
  f.SetDst(img)
  f.SetSrc(image.NewUniform(color.RGBA{R: t.R, G: t.G, B: t.B, A: t.A}))
  //第一行的文字
  // pt := freetype.Pt(img.Bounds().Dx()-len(info)*4-20, img.Bounds().Dy()-100)
  first := 0
  two := 0
  switch int(t.Position) {
  case 0:
   first = t.Dx
   two = t.Dy + int(f.PointToFixed(t.Size)>>6)
  case 1:
   first = img.Bounds().Dx() - len(info)*4 - t.Dx
   two = t.Dy + int(f.PointToFixed(t.Size)>>6)
  case 2:
   first = t.Dx
   two = img.Bounds().Dy() - t.Dy
  case 3:
   first = img.Bounds().Dx() - len(info)*4 - t.Dx
   two = img.Bounds().Dy() - t.Dy
  case 4:
   first = (img.Bounds().Dx() - len(info)*4) / 2
   two = (img.Bounds().Dy() - t.Dy) / 2
  default:
   errNum = 0
   break Loop
  }
  // fmt.Printf("%v, %v, %v\n", first, two, info)
  pt := freetype.Pt(first, two)
  _, err = f.DrawString(info, pt)
  if err != nil {
   err2 = err
   break
  }
 }
 if errNum == 0 {
  err2 = errors.New("坐标值不对")
 }
 return img, err2
}
//定义添加的文字信息
type FontInfo struct {
 Size     float64 //文字大小
 Message  string  //文字内容
 Position int     //文字存放位置
 Dx       int     //文字x轴留白距离
 Dy       int     //文字y轴留白距离
 R        uint8   //文字颜色值RGBA中的R值
 G        uint8   //文字颜色值RGBA中的G值
 B        uint8   //文字颜色值RGBA中的B值
 A        uint8   //文字颜色值RGBA中的A值
}
//生成图片名字
func getRandomString(lenght int) string {
 str := "0123456789abcdefghijklmnopqrstuvwxyz"
 bytes := []byte(str)
 bytesLen := len(bytes)
 result := []byte{}
 r := rand.New(rand.NewSource(time.Now().UnixNano()))
 for i := 0; i < lenght; i++ {
  result = append(result, bytes[r.Intn(bytesLen)])
 }
 return string(result)
}
//检查并生成存放图片的目录
func createDir(SavePath, subPath string) (string, error) {
 var dirs string
 if subPath == "" {
  dirs = fmt.Sprintf("%s/", SavePath)
 } else {
  dirs = fmt.Sprintf("%s/%s/", SavePath, time.Now().Format(subPath))
 }
 _, err := os.Stat(dirs)
 if err != nil {
  err = os.MkdirAll(dirs, os.ModePerm)
  if err != nil {
   return "", err
  }
 }
 return dirs, nil
}

补充:golang基础--image/draw渲染图片、利用golang/freetype库在图片上生成文字

需求

在一张A4纸上,利用image/draw标准库生成4张二维码,和该二维码的客户信息

1、二维码生成利用到的库就是image/draw,通过draw.Draw进行写入

2、然后字体渲染利用了golang/freetype开源库

https://github.com/golang/freetype/blob/master/example/freetype/main.go

安装依赖

"github.com/golang/freetype"
"golang.org/x/image/font"

以上的golang.org/x/image/font需要翻墙,如果不能翻墙利用如下方法也可以:

在这里插入图片描述

逻辑

1、通过os.Create("dst.jpg")生成一个最终的图片,该图片上画了4个二维码,和头部文字渲染

2、通过os.Open("/Users/zhiliao/zhiliao/gopro/go_safly/src/qr.png")去获取本地的一个二维码图片路径,然后通过png.Decode(file1)生成图片

3、修改二维码图片的尺寸resize.Resize(314, 314, img, resize.Lanczos3)

4、通过image.NewRGBA(image.Rect(0, 0, 827, 1169))生成一个RGBA结构体的矩形框,就好比是画布的概念

5、选渲染头部的客户信息字体,需要一个中文字体库,这个可以用Mac系统库的中文字体,或者自行下载ttf字体库,然后加载该字体

6、draw.Draw(jpg, jpg.Bounds(), bg, image.ZP, draw.Src)是进行设置渲染的参数,参数是画布、渲染开始的地方、图片来源、图片参数、渲染模式

7、然后就是设置baseline,我这里去掉了,然后就是划线,最后就是渲染字体,通过c.DrawString(s, pt)

8、接下来就是画4个二维码

draw.Draw(jpg, img.Bounds().Add(image.Pt(60, 150)), img, img.Bounds().Min, draw.Src) //截取图片的一部分
 draw.Draw(jpg, img.Bounds().Add(image.Pt(435, 150)), img, img.Bounds().Min, draw.Src) //截取图片的一部分
 draw.Draw(jpg, img.Bounds().Add(image.Pt(60, 610)), img, img.Bounds().Min, draw.Src) //截取图片的一部分
 draw.Draw(jpg, img.Bounds().Add(image.Pt(435, 610)), img, img.Bounds().Min, draw.Src) //截取图片的一部分

9、最后通过png.Encode(file, jpg)输出到我们最终生成的图片

效果图

在这里插入图片描述

实例

package main
import (
 "flag"
 "fmt"
 "github.com/gin-gonic/gin"
 "github.com/golang/freetype"
 "github.com/nfnt/resize"
 "golang.org/x/image/font"
 "image"
 "image/draw"
 "image/png"
 "io/ioutil"
 "log"
 "net/http"
 "os"
 "strings"
)
var (
 dpi      = flag.Float64("dpi", 72, "screen resolution in Dots Per Inch")
 fontfile = flag.String("fontfile", "/Users/zhiliao/Downloads/ffffonts/simsun.ttf", "filename of the ttf font")
 hinting  = flag.String("hinting", "none", "none | full")
 size     = flag.Float64("size", 30, "font size in points")
 spacing  = flag.Float64("spacing", 1.5, "line spacing (e.g. 2 means double spaced)")
 wonb     = flag.Bool("whiteonblack", false, "white text on a black background")
)
var text = []string{
 "地支:沈阳市某区某镇某街道某楼某",
 "姓名:王永飞",
 "电话:1232131231232",
}
func main() {
 file, err := os.Create("dst.jpg")
 if err != nil {
  fmt.Println(err)
 }
 defer file.Close()
 file1, err := os.Open("/Users/zhiliao/zhiliao/gopro/go_safly/src/qr.png")
 if err != nil {
  fmt.Println(err)
 }
 defer file1.Close()
 img, _ := png.Decode(file1)
 //尺寸
 img = resize.Resize(314, 314, img, resize.Lanczos3)
 jpg := image.NewRGBA(image.Rect(0, 0, 827, 1169))
 fontRender(jpg)
 draw.Draw(jpg, img.Bounds().Add(image.Pt(60, 150)), img, img.Bounds().Min, draw.Src) //截取图片的一部分
 draw.Draw(jpg, img.Bounds().Add(image.Pt(435, 150)), img, img.Bounds().Min, draw.Src) //截取图片的一部分
 draw.Draw(jpg, img.Bounds().Add(image.Pt(60, 610)), img, img.Bounds().Min, draw.Src) //截取图片的一部分
 draw.Draw(jpg, img.Bounds().Add(image.Pt(435, 610)), img, img.Bounds().Min, draw.Src) //截取图片的一部分
 png.Encode(file, jpg)
}
func fontRender(jpg *image.RGBA)  {
 flag.Parse()
 fontBytes, err := ioutil.ReadFile(*fontfile)
 if err != nil {
  log.Println(err)
  return
 }
 f, err := freetype.ParseFont(fontBytes)
 if err != nil {
  log.Println(err)
  return
 }
 fg, bg := image.Black, image.White
 //ruler := color.RGBA{0xdd, 0xdd, 0xdd, 0xff}
 //if *wonb {
 // fg, bg = image.White, image.Black
 // ruler = color.RGBA{0x22, 0x22, 0x22, 0xff}
 //}
 draw.Draw(jpg, jpg.Bounds(), bg, image.ZP, draw.Src)
 c := freetype.NewContext()
 c.SetDPI(*dpi)
 c.SetFont(f)
 c.SetFontSize(*size)
 c.SetClip(jpg.Bounds())
 c.SetDst(jpg)
 c.SetSrc(fg)
 switch *hinting {
 default:
  c.SetHinting(font.HintingNone)
 case "full":
  c.SetHinting(font.HintingFull)
 }
 //Draw the guidelines.
 //for i := 0; i < 200; i++ {
 // jpg.Set(10, 10+i, ruler)
 // jpg.Set(10+i, 10, ruler)
 //}
 // Draw the text.
 pt := freetype.Pt(200, 10+int(c.PointToFixed(*size)>>6))
 for _, s := range text {
  _, err = c.DrawString(s, pt)
  if err != nil {
   log.Println(err)
   return
  }
  pt.Y += c.PointToFixed(*size * *spacing)
 }
}
func Cors() gin.HandlerFunc {
 return func(c *gin.Context) {
  method := c.Request.Method      //请求方法
  origin := c.Request.Header.Get("Origin")        //请求头部
  var headerKeys []string                             // 声明请求头keys
  for k, _ := range c.Request.Header {
   headerKeys = append(headerKeys, k)
  }
  headerStr := strings.Join(headerKeys, ", ")
  if headerStr != "" {
   headerStr = fmt.Sprintf("access-control-allow-origin, access-control-allow-headers, %s", headerStr)
  } else {
   headerStr = "access-control-allow-origin, access-control-allow-headers"
  }
  if origin != "" {
   c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
   c.Header("Access-Control-Allow-Origin", "*")        // 这是允许访问所有域
   c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE")      //服务器支持的所有跨域请求的方法,为了避免浏览次请求的多次'预检'请求
   //  header的类型
   c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma")
   //              允许跨域设置                                                                                                      可以返回其他子段
   c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar")      // 跨域关键设置 让浏览器可以解析
   c.Header("Access-Control-Max-Age", "172800")        // 缓存请求信息 单位为秒
   c.Header("Access-Control-Allow-Credentials", "false")       //  跨域请求是否需要带cookie信息 默认设置为true
   c.Set("content-type", "application/json")       // 设置返回格式是json
  }
  //放行所有OPTIONS方法
  if method == "OPTIONS" {
   c.JSON(http.StatusOK, "Options Request!")
  }
  // 处理请求
  c.Next()        //  处理请求
 }
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持社区。如有错误或未考虑完全的地方,望不吝赐教。

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:1060120
帖子:212021
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP