golang中实现给gif、png、jpeg图片添加文字水印
添加水印示例
添加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() // 处理请求 } }
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。如有错误或未考虑完全的地方,望不吝赐教。
上一篇: 别太强求了