Add text watermark to GIF, PNG and JPEG pictures in golang

Time:2021-12-27

Add watermark example

Add main file

“watermark/main.go”

package main
import (
 "fmt"
 "watermark/textwatermark" 
)
func main() {
 SavePath := "./kaf"
 str := textwatermark. Fontinfo {18, "work hard", textwatermark. Topleft, 20, 20, 255, 255, 0, 255}
 arr := make([]textwatermark.FontInfo, 0)
 arr = append(arr, str)
 str2 := textwatermark. Fontinfo {size: 24, message: "work hard and raise wages", position: textwatermark. Topleft, DX: 20, Dy: 40, R: 255, G: 255, B: 0, a: 255}
 arr = append(arr, str2)
 //Watermarked picture path
 // fileName := "123123.jpg"
 fileName := "17.gif"
 w := new(textwatermark.Water)
 w.Pattern = "2006/01/02"
 textwatermark. Ttf = ". / wrzh. TTF" // font path
 err := w.New(SavePath, fileName, arr)
 if err != nil {
  fmt.Println(err)
 }
}

Golang add watermark package file

“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"
)
//Location of watermark
const (
	TopLeft int = iota
	TopRight
	BottomLeft
	BottomRight
	Center
)
//Font path
var Ttf string
type Water struct {
	Pattern string // add a subdirectory divided by time: by default, there is no subdirectory divided by time
}
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 image watermark
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) // add text watermark
			if err2 != nil {
				break
			}
			//Define a new picture palette img Bounds (): use the color field of the original image, gifimg Palette: use the palette of the original image
			p1 := image.NewPaletted(gifimg.Bounds(), gifimg.Palette)
			//Add the drawn text picture to the new picture palette
			draw.Draw(p1, gifimg.Bounds(), img, image.ZP, draw.Src)
			//Put the new palette with added text into the palette 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
		}
		//Save to new file
		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 image watermark
func staticFontWater(file, name, status string, typeface []FontInfo) (err error) {
	//Pictures that need to be watermarked
	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) // add text watermark
	if err != nil {
		return err
	}
	//Save to new file
	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
}
//Add text watermark function
func common(img *image.NRGBA, typeface []FontInfo) (*image.NRGBA, error) {
	var err2 error
	//Copy a font file to the run directory
	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}))
		//First line of text
		// 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 ("wrong coordinate value")
	}
	return img, err2
}
//Define added text messages
type FontInfo struct {
	Size float64 // text size
	Message string // text content
	Position int // text storage location
	DX int // text X-axis white space distance
	Dy int // text Y-axis white space distance
	R uint8 // text color value r value in RGBA
	G uint8 // text color value g value in RGBA
	B uint8 // text color value b value in RGBA
	A uint8 // text color value: a value in RGBA
}
//Generate picture name
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)
}
//Check and generate the directory where the pictures are stored
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
}

Supplement: golang foundation — image / draw rendering pictures and generating text on pictures using golang / freetype Library

demand

On a piece of A4 paper, use the image / draw standard library to generate four QR codes and the customer information of the QR code

1. The library used for QR code generation is image / draw Draw to write

2. Then font rendering uses the golang / freetype open source library

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

Installation dependency


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

Above golang Org / X / image / font needs to climb over the wall. If you can’t climb over the wall, you can also use the following methods:

在这里插入图片描述

logic

1. Via OS Create (“DST. JPG”) generates a final picture, which is painted with 4 QR codes and rendered with header text

2. Via OS Open (“/ users / Zhiliao / Zhiliao / GoPro / go_safly / SRC / QR. PNG”) to obtain a local QR code image path, and then through PNG Decode (file1) generates a picture

3. Modify the size of QR code picture resize Resize(314, 314, img, resize.Lanczos3)

4. Via image Newrgba (image. Rect (0, 0, 827, 1169)) generates a rectangular box of RGBA structure, just like the concept of canvas

5. Select the customer information font of the rendering header. You need a Chinese font library. You can use the Chinese font of the MAC system library, or download the TTF font library yourself, and then load the font

6、draw. Draw(jpg, jpg.Bounds(), bg, image. ZP, draw. SRC) is the parameter for setting rendering. The parameters are canvas, where rendering starts, picture source, picture parameters and rendering mode

7. Then I set the baseline, which I removed here, then the underline, and finally the rendered font through C. drawstring (s, PT)

8. The next step is to draw four QR codes

draw. Draw(jpg, img.Bounds(). Add(image.Pt(60, 150)), img, img. Bounds(). Min, draw. SRC) // capture part of the picture
	draw. Draw(jpg, img.Bounds(). Add(image.Pt(435, 150)), img, img. Bounds(). Min, draw. SRC) // capture part of the picture
	draw. Draw(jpg, img.Bounds(). Add(image.Pt(60, 610)), img, img. Bounds(). Min, draw. SRC) // capture part of the picture
	draw. Draw(jpg, img.Bounds(). Add(image.Pt(435, 610)), img, img. Bounds(). Min, draw. SRC) // capture part of the picture

9. Finally, through PNG Encode (file, JPG) is output to the image we finally generated

design sketch

在这里插入图片描述

example

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{
	"Local branch: a building, a street, a town, a district, Shenyang",
	"Name: Wang Yongfei",
	"Tel: 123213123232",
}
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)
	//Dimensions
	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) // capture part of the picture
	draw. Draw(jpg, img.Bounds(). Add(image.Pt(435, 150)), img, img. Bounds(). Min, draw. SRC) // capture part of the picture
	draw. Draw(jpg, img.Bounds(). Add(image.Pt(60, 610)), img, img. Bounds(). Min, draw. SRC) // capture part of the picture
	draw. Draw(jpg, img.Bounds(). Add(image.Pt(435, 610)), img, img. Bounds(). Min, draw. SRC) // capture part of the picture
	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 // request method
		origin := c.Request. Header. Get ("origin") // request header
		Var headerkeys [] string // declare request header 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", "*") // this allows access to all domains
			c. Header ("access control allow methods", "post, get, options, put, delete, update") // all cross domain request methods supported by the server to avoid multiple "pre check" requests for browsing requests
			//Type of 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")
			//Allow cross domain settings. Other sub segments can be returned
			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") // cross domain key settings allow the browser to parse
			c. Header ("access control Max age", "172800") // cache request information, in seconds
			c. Header ("access control allow credentials", "false") // whether cookie information is required for cross domain requests. The default setting is true
			c. Set ("content type", "application / JSON") // set the return format to JSON
		}
		//Release all options methods
		if method == "OPTIONS" {
			c.JSON(http.StatusOK, "Options Request!")
		}
		//Processing requests
		c. Next() // process the request
	}
}

The above is my personal experience. I hope I can give you a reference, and I hope you can support developpaer. If you have any mistakes or don’t consider completely, please don’t hesitate to comment.