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语言-在mac下brew升级golang
Apr 25 Golang
Go语言中break label与goto label的区别
Apr 28 Golang
浅谈Golang 嵌套 interface 的赋值问题
Apr 29 Golang
解决Goland 同一个package中函数互相调用的问题
May 06 Golang
Golang的继承模拟实例
Jun 30 Golang
golang中的struct操作
Nov 11 Golang
Go语言实现一个简单的并发聊天室的项目实战
Mar 18 Golang
简单聊聊Golang中defer预计算参数
Mar 25 Golang
Golang 1.18 多模块Multi-Module工作区模式的新特性
Apr 11 Golang
Go获取两个时区的时间差
Apr 20 Golang
Golang bufio详细讲解
Apr 21 Golang
Go调用Rust方法及外部函数接口前置
Jun 14 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
curl不使用文件存取cookie php使用curl获取cookie示例
2014/01/26 PHP
PHP实现的汉字拼音转换和公历农历转换类及使用示例
2014/07/01 PHP
PHP7导出Excel报ERR_EMPTY_RESPONSE解决方法
2019/04/16 PHP
extjs 学习笔记(二) Ext.Element类
2009/10/13 Javascript
Javascript中正则表达式的全局匹配模式分析
2011/04/26 Javascript
js中parseFloat(参数1,参数2)定义和用法及注意事项
2013/01/27 Javascript
裁剪字符串trim()自定义改进版
2013/04/10 Javascript
Extjs4 Treegrid 使用心得分享(经验篇)
2013/07/01 Javascript
javascript生成随机颜色示例代码
2014/05/05 Javascript
JS右下角广告窗口代码(可收缩、展开及关闭)
2015/09/04 Javascript
JS创建事件的三种方法(实例代码)
2016/05/12 Javascript
jQuery图片轮播插件——前端开发必看
2016/05/31 Javascript
详解JS去重及字符串奇数位小写转大写
2016/12/29 Javascript
js仿网易表单及时验证功能
2017/03/07 Javascript
详解Vue.js组件可复用性的混合(mixin)方式和自定义指令
2017/09/06 Javascript
ES6中javascript实现函数绑定及类的事件绑定功能详解
2017/11/08 Javascript
纯js代码生成可搜索选择下拉列表的实例
2018/01/11 Javascript
mock.js实现模拟生成假数据功能示例
2019/01/15 Javascript
Node.js实现一个HTTP服务器的方法示例
2019/05/13 Javascript
vue 公共列表选择组件,引用Vant-UI的样式方式
2020/11/02 Javascript
JavaScript手写数组的常用函数总结
2020/11/22 Javascript
python检测lvs real server状态
2014/01/22 Python
python实现关键词提取的示例讲解
2018/04/28 Python
python pandas 对series和dataframe的重置索引reindex方法
2018/06/07 Python
详解如何在Apache中运行Python WSGI应用
2019/01/02 Python
详解Python二维数组与三维数组切片的方法
2019/07/18 Python
Python秒算24点实现及原理详解
2019/07/29 Python
Python和Anaconda和Pycharm安装教程图文详解
2020/02/04 Python
浅谈python量化 双均线策略(金叉死叉)
2020/06/03 Python
Probikekit欧盟:在线公路自行车专家
2019/07/12 全球购物
应聘医学检验人员自荐信
2013/09/27 职场文书
保险内勤岗位职责
2014/04/05 职场文书
气象学专业个人求职信
2014/04/22 职场文书
公司档案管理制度
2015/08/05 职场文书
入党后的感想
2015/08/10 职场文书
pytorch 带batch的tensor类型图像显示操作
2021/05/20 Python