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


Posted in Golang onApril 26, 2021

添加水印示例

添加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需要翻墙,如果不能翻墙利用如下方法也可以:

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

逻辑

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)输出到我们最终生成的图片

效果图

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

实例

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()        //  处理请求
	}
}

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

Golang 相关文章推荐
Go Gin实现文件上传下载的示例代码
Apr 02 Golang
go原生库的中bytes.Buffer用法
Apr 25 Golang
go语言中GOPATH GOROOT的作用和设置方式
May 05 Golang
go语言中fallthrough的用法说明
May 06 Golang
GoLang中生成UUID唯一标识的实现
May 08 Golang
Golang二维数组的使用方式
May 28 Golang
Go timer如何调度
Jun 09 Golang
如何解决goland,idea全局搜索快捷键失效问题
Apr 03 Golang
Go获取两个时区的时间差
Apr 20 Golang
GoFrame gredis缓存DoVar Conn连接对象 自动序列化GoFrame gredisDo/DoVar方法Conn连接对象自动序列化/反序列化总结
Jun 14 Golang
在ubuntu下安装go开发环境的全过程
Aug 05 Golang
基于Python实现西西成语接龙小助手
Aug 05 Golang
Go语言带缓冲的通道实现
Apr 26 #Golang
go语言求任意类型切片的长度操作
Apr 26 #Golang
golang如何去除多余空白字符(含制表符)
Apr 25 #Golang
用golang如何替换某个文件中的字符串
Apr 25 #Golang
Golang 正则匹配效率详解
golang正则之命名分组方式
Apr 25 #Golang
go语言-在mac下brew升级golang
Apr 25 #Golang
You might like
PHP动态变静态原理
2006/11/25 PHP
谈谈关于php的优点与缺点
2013/04/11 PHP
PHP模板引擎Smarty内置变量调解器用法详解
2016/04/11 PHP
Laravel框架在本地虚拟机快速安装的方法详解
2018/06/11 PHP
一个JavaScript继承的实现
2006/10/24 Javascript
Prototype String对象 学习
2009/07/19 Javascript
js取得url地址参数实例
2013/02/22 Javascript
Node.js实现的简易网页抓取功能示例
2014/12/05 Javascript
Nodejs初级阶段之express
2015/11/23 NodeJs
JS在一定时间内跳转页面及各种刷新页面的实现方法
2016/05/26 Javascript
jQuery检查元素存在性(推荐)
2016/09/17 Javascript
使用Angular缓存父页面数据的方法
2017/01/03 Javascript
微信小程序 动态绑定数据及动态事件处理
2017/03/14 Javascript
JavaScript 过滤关键字
2017/03/20 Javascript
JavaScript基于activexobject连接远程数据库SQL Server 2014的方法
2017/07/12 Javascript
Angular实现的进度条功能示例
2018/02/18 Javascript
利用d3.js实现蜂巢图表带动画效果
2019/09/03 Javascript
vue+vuex+axios从后台获取数据存入vuex,组件之间共享数据操作
2020/07/31 Javascript
400多行Python代码实现了一个FTP服务器
2012/05/10 Python
python生成器表达式和列表解析
2016/03/10 Python
网站渗透常用Python小脚本查询同ip网站
2017/05/08 Python
python爬虫超时的处理的实例
2018/12/19 Python
使用turtle绘制五角星、分形树
2019/10/06 Python
节日快乐! Python画一棵圣诞树送给你
2019/12/24 Python
用Python绘制漫步图实例讲解
2020/02/26 Python
TensorFlow keras卷积神经网络 添加L2正则化方式
2020/05/22 Python
pycharm远程连接vagrant虚拟机中mariadb数据库
2020/06/05 Python
python爬虫实现爬取同一个网站的多页数据的实例讲解
2021/01/18 Python
瑞士香水购物网站:Parfumcity.ch
2017/01/14 全球购物
美国户外服装和装备购物网站:Outland USA
2020/03/22 全球购物
2014年质检员工作总结
2014/11/18 职场文书
2014年学校后勤工作总结
2014/12/06 职场文书
2015年文员个人工作总结
2015/04/09 职场文书
活动宣传稿范文
2015/07/23 职场文书
mybatis使用oracle进行添加数据的方法
2021/04/27 Oracle
JavaScript实现班级抽签小程序
2021/05/19 Javascript