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

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

(0)

相关推荐

  • Golang汇编命令解读及使用

    我们可以很容易将一个golang程序转变成汇编语言. 比如我写了一个main.go: package main func g(p int) int { return p+1; } func main() { c := g(4) + 1 _ = c } 使用命令: GOOS=linux GOARCH=386 go tool compile -S main.go >> main.S 我们就获取了main.S是main.go的汇编版本. "".g t=1 size=16 valu

  • go语言求任意类型切片的长度操作

    最近用go写程序时遇到一个问题--求任意类型切片的长度. 作为一个初学者,刚刚学了接口和切片,知道了每个类型都实现了一个空接口interface{},那么如果接口类型作为函数的参数,那它应该是可以接收任意类型的实参的 带着这样的想法就写出了下面的代码: func size(ins []interface{}) int { return len(ins) } 然后调用 a := []int{1, 2, 3, 4} fmt.Println(size(a)) 但编译的时候报了以下错误: cannot

  • go语言中的二维切片赋值

    先用append填充一维的,然后将一维append到二维 代码如下 var a [][]int for i := 0; i < 10; i++ { var tmp []int for j:= 0; j < 10; j++ { tmp = append(tmp, j) } a = append(a, tmp) } 补充:Go切片的一些技巧 空切片也是切片 与map不同,尚未分配的切片也是可以用的: 一个nil切片的长度和容量都是0:你可以append到一个空切片,Go会自动分配:你也可以直接迭代

  • golang 切片的三种使用方式及区别的说明

    概念 切片(slice)是建立在数组之上的更方便,更灵活,更强大的数据结构.切片并不存储任何元素而只是对现有数组的引用. 三种方式及细节案例 ①定义一个切片,然后让切片去引用一个已经创建好的数组 package main import ( "fmt" ) func main() { var arr [5]int = [...]int {1, 2, 3, 4, 5} var slice = arr[1:3] fmt.Println("arr=", arr) fmt.P

  • 解决golang中container/list包中的坑

    golang中list包用法可以参看这篇文章 但是list包中大部分对于e *Element进行操作的元素都可能会导致程序崩溃,其根本原因是e是一个Element类型的指针,当然其也可能为nil,但是golang中list包中函数没有对其进行是否为nil的检查,变默认其非nil进行操作,所以这种情况下,便可能出现程序崩溃. 1.举个简单例子 Remove()函数 package main import ( "container/list" "fmt" ) func

  • GO语言实现批量压缩图片和水印

    前段时间想做个图片站,就用手机照了很多相片,但是要一个个用PS去压缩修改尺寸太麻烦了.最后想到了用golang去实现,算是边学边练吧.其中用到了github.com/nfnt/resize这个第三方库,仅仅支持JPG图片格式. 复制代码 代码如下: package main import (     "fmt"     "github.com/nfnt/resize"     "image"     "image/draw"

  • golang 删除切片的某个元素及剔除切片内的零值方式

    看代码吧~ func remove(slice []interface{}, elem interface{}) []interface{}{ if len(slice) == 0 { return slice } for i, v := range slice { if v == elem { slice = append(slice[:i], slice[i+1:]...) return remove(slice,elem) break } } return slice } func rem

  • golang-切片slice的创建方式

    在创建一个新的切片是都会先创建一个长度为len的数组,并为其开辟一个cap长度为cap的额外空间,所以在cap范围类增加元素,数组的起始地址不会改变,否则会创建一个新的数组,即起始的位置发生改变 数组创建 代码 intArr:=[...]int{1,2,3,4,5,6,7,9} //方式一指定开始和结束 s:=intArr[1:3] //方式er指定开始和结束+容量 s:=intArr[1:3:6] 创建后的slice [a: b :c] a:起始位置 b:截取数据的结束位置 默认到末端) c:

  • 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, 2

  • php给图片添加文字水印方法汇总

    1: 面向过程的编写方法 //指定图片路径 $src = '001.png'; //获取图片信息 $info = getimagesize($src); //获取图片扩展名 $type = image_type_to_extension($info[2],false); //动态的把图片导入内存中 $fun = "imagecreatefrom{$type}"; $image = $fun('001.png'); //指定字体颜色 $col = imagecolorallocateal

  • php面向对象与面向过程两种方法给图片添加文字水印

    目前绝大多数PHP程序员使用面向过程的方式,因为解析WEB页面本身就非常"过程化"(从一个标签到另一个标签).在HTML中嵌入过程处理代码是很直接自然的作法,所以PHP程序员通常使用这种方式. 如果你是刚接触PHP,用面向过程的风格来书写代码很可能是你唯一的选择.但是如果你经常上PHP论坛和新闻组的话,你应该会看到有关"对象"的文章.你也可能看到过如何书写面向对象的PHP代码的教程.或者你也可能下载过一些现成的类库,并尝试着去实例化其中的对象和使用类方法--尽管你可

  • Python 使用 Pillow 模块给图片添加文字水印的方法

    像微博一类的平台上传图片时,平台都会添加一个水印,宣誓着对图片的所有权,我们自己的博客平台也可以给自己的图片添加上水印. 还是用 Pillow 模块来实现 先来看一个简单的例子 >>> from PIL import Image >>> from PIL import ImageDraw >>> >>> image = Image.open('/Users/wxnacy/Downloads/vm-error1.png') >&g

  • php图片添加文字水印实现代码

    php类库给现有的图片加文字水印,代码不是很完善,欢迎大家多多指教!代码如下: <?php /*PHP图片加文字水印类库 QQ:3697578482 伤心的歌 该类库暂时只支持文字水印,位置为右下角,颜色随机 调用方法: 1.在需要加水印的文件顶部引入类库: include_once 'imageClass.php'; 2.声明新类: $tpl=new image_fu; 3.给图片水印提供参数: $tpl->img(图片路径,水印文字,字体路径,字体大小,字体角度); 比如:$tpl->

  • python实现图片加文字水印OPenCV和PIL库

    目录 一:openCV给图片添加水印 二:使用PIL给图片添加水印 在python中我们可以使用openCV给图片添加水印,这里注意openCV无法添加汉字水印,添加汉字水印上可使用PIL库给图片添加水印 一:openCV给图片添加水印 1:安装openCV pip install opencv-python 2:使用openCV给图片添加水印实例: # -*- coding: utf-8 -*- import cv2 # 载入突破 img = cv2.imread('test.jpg') #

  • php给图片加文字水印

    注释非常的详细了,这里就不多废话了 <?php /*给图片加文字水印的方法*/ $dst_path = 'http://f4.topitme.com/4/15/11/1166351597fe111154l.jpg'; $dst = imagecreatefromstring(file_get_contents($dst_path)); /*imagecreatefromstring()--从字符串中的图像流新建一个图像,返回一个图像标示符,其表达了从给定字符串得来的图像 图像格式将自动监测,只要

  • c#给图片添加文字的代码小结

    代码实例一 复制代码 代码如下: using System; using System.IO; using System.Collections; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; namespace Imag_writer { /// <summary> /// 水印的类型 /// </summary> public enum WaterMarkT

  • PHP添加文字水印或图片水印的水印类完整源代码与使用示例

    PHP实现的给图片添加水印功能,可添加文字水印或图片水印,使用文字水印时需要提供字体文件,使用图片水印时需要提供水印图片,水印图片不能比要添加水印的图片大,请使用背景透明的水印图片. 该水印类支持自定义水印位置.自定义水印大小和水印的透明度,字体水印可自定义颜色等,功能已相应完善. 完整源代码如下(注解中已给出使用示例): <?php /** * 图片加水印类,支持文字水印.透明度设置.自定义水印位置等. * 使用示例: * $obj = new WaterMask($imgFileName);

  • Python实现图片添加文字

    在工作中有时候会给图上添加文字,常用的是PS工具,不过我想通过代码的方式来给图片添加文字. 需要使用的Python的图像库:PIL.更加详细的知识点如下: Imaga模块:用来创建,打开,保存图片文件 new(path):用来创建一个新的图片文件.该文件位于path所在的路径中..打开后返回Image类型的图片. open(path):用来打开已经存在的图片文件.该文件位于path所在的路径中.打开后返回Image类型的图片. save(path):用来把创建或者打开的图片保到path所在的路径

随机推荐