欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-03-17 22:52:42
添加水印示例添加main文件:“watermark/main.go”package mainimport ( "fmt" "watermark/textwatermark" )func main()...

添加水印示例

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

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