【Go】:图片上添加水印的全面指南——从基础到高级特性

前言

在数字内容日益重要的今天,保护版权和标识来源变得关键。为图片添加水印有助于声明所有权、提升品牌认知度,并防止未经授权的使用。本文将介绍如何用Go语言实现图片水印,包括静态图片和带旋转、倾斜效果的文字水印,帮助您有效保护数字内容。我们将逐步解析关键步骤,确保清晰易懂。

一、准备工作

为了顺利实现图片水印功能,您需要完成以下几个准备步骤:

1.安装Go语言环境:确保您的开发环境中已经安装了Go语言,并具备基本的Go编程知识。

2.安装必要的库

  • golang.org/x/image/draw:支持高质量缩放及其他图像绘制操作。
  • github.com/disintegration/imaging:提供简便的API用于图像变换,如旋转和倾斜。

3.准备图像资源

  • 主图 (Base Image):这是您想要添加水印的原始图像。它可以是任何您有权处理的图像文件。
  • 水印图 (Watermark Image):这是将被放置在主图之上的图像,通常是一个透明背景的PNG文件,这样可以确保它不会遮挡主图的重要细节。

确保您拥有上述所有工具和资源后,就可以开始编写代码来实现图片水印功能了。接下来的章节将逐步指导您如何加载主图、应用水印图并保存最终结果。

二、图片加水印

2.1 图片水印

2.1.1 打开主图

首先,我们需要打开并读取主图文件。这一步确保了程序能够访问到用户想要处理的原始图像。

// 打开主图文件
mainImageFile, err := os.Open("main.png")
if err != nil {log.Fatalf("Failed to open main image: %v", err)
}
defer mainImageFile.Close()

2.1.2 解码主图

接下来,从输入流中读取原始图像并解码它。如果解码过程中出现问题,程序将返回错误信息。这里我们使用image.Decode函数自动识别图像格式。

mainImageFile, err := os.Open("main.png")
if err != nil {log.Fatalf("Failed to open main image: %v", err)
}
defer mainImageFile.Close()

2.1.3 打开水印图片

然后,我们需要打开水印图片文件。与主图类似,我们也需要确保能够正确读取和解码水印图像。

// 打开水印图片
watermarkImageFile, err := os.Open("logo.png") // 可以替换为其他图片文件名
if err != nil {log.Fatalf("Failed to open watermark image: %v", err)
}
defer watermarkImageFile.Close()

2.1.4 解码水印图片

接下来,从输入流中读取水印图像并解码它。如果解码过程中出现问题,程序将返回错误信息。这里我们再次使用image.Decode函数自动识别图像格式。

// 解码水印
watermarkImage, _, err := image.Decode(watermarkImageFile)
if err != nil {log.Fatalf("Failed to decode watermark image: %v", err)
}

2.1.5 计算缩放比例

为了保证水印不会过于显眼或遮挡过多内容,根据原始图像的尺寸计算水印的最大宽度和高度。通常,我们会设定最大值为原始图像宽高的25%。然后基于这些最大值计算出适当的缩放比例。

// 获取主图和水印的边界矩形
mainImageBounds := mainImage.Bounds()
watermarkImageBounds := watermarkImage.Bounds()// 计算水印的最大尺寸
maxWatermarkWidth := int(float64(mainImageBounds.Max.X) * 0.25)  // 最大宽度为主图宽度的25%
maxWatermarkHeight := int(float64(mainImageBounds.Max.Y) * 0.25) // 最大高度为主图高度的25%// 计算水印的缩放比例
scale := 1.0
if watermarkImageBounds.Max.X > maxWatermarkWidth || watermarkImageBounds.Max.Y > maxWatermarkHeight {scale = math.Min(float64(maxWatermarkWidth)/float64(watermarkImageBounds.Max.X),float64(maxWatermarkHeight)/float64(watermarkImageBounds.Max.Y),)
}// 应用缩放比例
watermarkWidth := int(float64(watermarkImageBounds.Max.X) * scale)
watermarkHeight := int(float64(watermarkImageBounds.Max.Y) * scale)

2.1.6 创建新的图像

创建一个新的RGBA图像,其大小与原始图像相同,并将原始图像复制到这个新图像中。

// 创建一个新的图像,大小与主图相同
resultImage := image.NewRGBA(mainImageBounds)// 将主图复制到新图像中
draw.Draw(resultImage, mainImageBounds, mainImage, mainImageBounds.Min, draw.Src)

2.1.7 缩放水印图像

根据前面计算的缩放比例调整水印图像的大小。我们可以使用golang.org/x/image/draw包中的draw.CatmullRom.Scale方法来进行高质量缩放。

// 创建一个用于存放缩放后水印的新图像
resizedWatermarkImage := image.NewRGBA(image.Rect(0, 0, watermarkWidth, watermarkHeight))// 使用高质量缩放算法缩放水印图像
draw.CatmullRom.Scale(resizedWatermarkImage, resizedWatermarkImage.Bounds(), watermarkImage, watermarkImageBounds, draw.Over, nil)

2.1.8 确定水印位置

根据用户提供的参数确定水印应该放置的位置,例如左上角、右上角等。对于每个预设的位置,我们计算出相应的坐标点。这里仅给出右下角的例子:

// 引入 position 变量,并赋值为一个有效的水印位置常量
position := "left_top" // 假设使用 "left_top" 作为示例// 计算水印放置的位置
var watermarkX, watermarkY int
switch position {
case "left_top":watermarkX = int(float64(mainImageBounds.Max.X) * 0.02) // 2% of the widthwatermarkY = int(float64(mainImageBounds.Max.Y) * 0.02) // 2% of the height
case "right_top":watermarkX = int(float64(mainImageBounds.Max.X)*0.98) - watermarkWidth // 98% of the width minus watermark widthwatermarkY = int(float64(mainImageBounds.Max.Y) * 0.02)                // 2% of the height
case "left_bottom":watermarkX = int(float64(mainImageBounds.Max.X) * 0.02)                 // 2% of the widthwatermarkY = int(float64(mainImageBounds.Max.Y)*0.98) - watermarkHeight // 98% of the height minus watermark height
case "right_bottom":watermarkX = int(float64(mainImageBounds.Max.X)*0.98) - watermarkWidth  // 98% of the width minus watermark widthwatermarkY = int(float64(mainImageBounds.Max.Y)*0.98) - watermarkHeight // 98% of the height minus watermark height
default:log.Fatalf("Invalid watermark position: %v", position)
}

2.1.9 绘制水印

最后,使用draw.Draw方法将调整后的水印绘制到新图像的指定位置。

// 将水印绘制到新图像的指定位置
draw.Draw(resultImage, image.Rectangle{Min: image.Point{X: watermarkX, Y: watermarkY},Max: image.Point{X: watermarkX + watermarkWidth, Y: watermarkY + watermarkHeight},
}, resizedWatermarkImage, image.Point{X: 0, Y: 0}, draw.Over)

2.1.10 绘制旋转水印(可选)

为了让水印更加多样化,可以引入旋转或倾斜的效果。这可以通过创建一个仿射变换矩阵并应用于文字图像来完成。以下是实现旋转功能的代码片段:

// 创建一个新的图像,大小与水印相同
rotatedWatermarkImage := image.NewRGBA(resizedWatermarkImage.Bounds())// 引入 rotation 变量,并赋值为一个有效的旋转角度(度数)
rotation := 45.0 // 假设使用 45.0 度作为示例// 计算旋转角度的弧度
radians := rotation * math.Pi / 180.0// 计算旋转后的中心点
centerX := float64(watermarkWidth) / 2.0
centerY := float64(watermarkHeight) / 2.0// 遍历每个像素点并应用旋转
for y := 0; y < watermarkHeight; y++ {for x := 0; x < watermarkWidth; x++ {// 将像素点转换为相对于中心点的坐标relX := float64(x) - centerXrelY := float64(y) - centerY// 应用旋转矩阵newX := relX*math.Cos(radians) - relY*math.Sin(radians)newY := relX*math.Sin(radians) + relY*math.Cos(radians)// 将旋转后的坐标转换回图像坐标newX += centerXnewY += centerY// 如果旋转后的坐标在图像范围内,则绘制像素if newX >= 0 && newX < float64(watermarkWidth) && newY >= 0 && newY < float64(watermarkHeight) {rotatedWatermarkImage.Set(int(newX), int(newY), resizedWatermarkImage.At(x, y))}}
}// 将旋转后的水印绘制到新图像的指定位置
draw.Draw(resultImage, image.Rectangle{Min: image.Point{X: watermarkX, Y: watermarkY}, Max: image.Point{X: watermarkX + watermarkWidth, Y: watermarkY + watermarkHeight}}, rotatedWatermarkImage, image.Point{X: 0, Y: 0}, draw.Over)

2.1.11 保存结果图像

根据原始图像的格式(如PNG或JPEG),将带有水印的新图像编码并保存到内存中的缓冲区,然后再写入磁盘。

// 保存结果图像到内存
var buffer bytes.Buffer
switch fileExtension {
case ".png":err = png.Encode(&buffer, resultImage)
case ".jpg", ".jpeg":err = jpeg.Encode(&buffer, resultImage, nil)
default:log.Fatalf("Unsupported file extension: %v", fileExtension)
}
if err != nil {log.Fatalf("Failed to encode image: %v", err)
}// 保存结果图像到文件
outputFileName := "output" + fileExtension
outputFile, err := os.Create(outputFileName)
if err != nil {log.Fatalf("Failed to create output file: %v", err)
}
defer outputFile.Close()// 将内存中的图像数据写入文件
_, err = buffer.WriteTo(outputFile)
if err != nil {log.Fatalf("Failed to write to output file: %v", err)
}

2.1.12 完整代码和效果

package mainimport ("bytes""golang.org/x/image/draw""image""image/jpeg""image/png""log""os""path/filepath"
)func main() {// 打开主图文件mainImageFile, err := os.Open("main.png")if err != nil {log.Fatalf("Failed to open main image: %v", err)}defer mainImageFile.Close()// 获取文件扩展名fileExtension := filepath.Ext(mainImageFile.Name())// 解码主图mainImage, _, err := image.Decode(mainImageFile)if err != nil {log.Fatalf("Failed to decode main image: %v", err)}// 打开水印图片watermarkImageFile, err := os.Open("logo.png") // 你可以将 "logo.png" 替换为 "logo.jpg" 或其他图片文件名if err != nil {log.Fatalf("Failed to open watermark image: %v", err)}defer watermarkImageFile.Close()// 解码水印watermarkImage, _, err := image.Decode(watermarkImageFile)if err != nil {log.Fatalf("Failed to decode watermark image: %v", err)}// 获取主图和水印的边界矩形mainImageBounds := mainImage.Bounds()watermarkImageBounds := watermarkImage.Bounds()// 计算水印的最大尺寸maxWatermarkWidth := int(float64(mainImageBounds.Max.X) * 0.20)  // 你可以将 "0.20" 替换为 "0.15" 或其他值maxWatermarkHeight := int(float64(mainImageBounds.Max.Y) * 0.20) // 你可以将 "0.20" 替换为 "0.15" 或其他值// 计算水印的缩放比例watermarkWidth := watermarkImageBounds.Max.XwatermarkHeight := watermarkImageBounds.Max.Y// 计算缩放比例scale := 1.0if watermarkWidth > maxWatermarkWidth {scale = float64(maxWatermarkWidth) / float64(watermarkWidth)}if watermarkHeight > maxWatermarkHeight {if scale > float64(maxWatermarkHeight)/float64(watermarkHeight) {scale = float64(maxWatermarkHeight) / float64(watermarkHeight)}}// 应用缩放比例watermarkWidth = int(float64(watermarkWidth) * scale)watermarkHeight = int(float64(watermarkHeight) * scale)// 创建一个新的图像,大小与主图相同resultImage := image.NewRGBA(mainImageBounds)// 将主图复制到新图像中draw.Draw(resultImage, mainImageBounds, mainImage, mainImageBounds.Min, draw.Src)// 缩放水印图像resizedWatermarkImage := image.NewRGBA(image.Rect(0, 0, watermarkWidth, watermarkHeight))draw.NearestNeighbor.Scale(resizedWatermarkImage, resizedWatermarkImage.Bounds(), watermarkImage, watermarkImageBounds, draw.Over, nil)// 引入 position 变量,并赋值为一个有效的水印位置常量position := "left_top" // 假设使用 "left_top" 作为示例// 计算水印放置的位置var watermarkX, watermarkY intswitch position {case "left_top":watermarkX = int(float64(mainImageBounds.Max.X) * 0.02) // 宽度的2%watermarkY = int(float64(mainImageBounds.Max.Y) * 0.02) // 高度的2%case "right_top":watermarkX = int(float64(mainImageBounds.Max.X)*0.98) - watermarkWidth // 宽度的98%减去水印宽度watermarkY = int(float64(mainImageBounds.Max.Y) * 0.02)                // 高度的2%case "left_bottom":watermarkX = int(float64(mainImageBounds.Max.X) * 0.02)                 // 宽度的2%watermarkY = int(float64(mainImageBounds.Max.Y)*0.98) - watermarkHeight // 高度的98%减去水印高度case "right_bottom":watermarkX = int(float64(mainImageBounds.Max.X)*0.98) - watermarkWidth  // 宽度的98%减去水印宽度watermarkY = int(float64(mainImageBounds.Max.Y)*0.98) - watermarkHeight // 高度的98%减去水印高度default:log.Fatalf("Invalid watermark position: %v", position)}// 将水印绘制到新图像的指定位置draw.Draw(resultImage, image.Rectangle{Min: image.Point{X: watermarkX, Y: watermarkY}, Max: image.Point{X: watermarkX + watermarkWidth, Y: watermarkY + watermarkHeight}}, resizedWatermarkImage, image.Point{X: 0, Y: 0}, draw.Over)// 保存结果图像到内存var buffer bytes.Bufferswitch fileExtension {case ".png":err = png.Encode(&buffer, resultImage)case ".jpg", ".jpeg":err = jpeg.Encode(&buffer, resultImage, nil)default:log.Fatalf("Unsupported file extension: %v", fileExtension)}if err != nil {log.Fatalf("Failed to encode image: %v", err)}// 保存结果图像到文件outputFile, err := os.Create("output" + fileExtension)if err != nil {log.Fatalf("Failed to create output file: %v", err)}defer outputFile.Close() // 添加文件关闭操作// 将内存中的图像数据写入文件_, err = buffer.WriteTo(outputFile)if err != nil {log.Fatalf("Failed to write to output file: %v", err)}
}

2.2 文字水印

敬请期待!!!

总结

通过以上步骤,我们不仅完成了在图片上添加静态图片水印的功能实现,还增加了旋转、倾斜的水印功能,使得生成的水印更加多样化和个性化。您可以根据自己的需求进一步优化代码,比如支持更多的水印位置选项,或者允许用户上传自定义水印图片。希望这篇文章能帮助您理解和实现这一常见但非常有用的功能。如果您有任何问题或遇到困难,请随时查阅相关文档或寻求社区的帮助。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/65628.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

springCloudGateWay使用总结

1、什么是网关 功能: ①身份认证、权限验证 ②服务器路由、负载均衡 ③请求限流 2、gateway搭建 2.1、创建一个空项目 2.2、引入依赖 2.3、加配置 3、断言工厂 4、过滤工厂 5、全局过滤器 6、跨域问题

【UE5 C++课程系列笔记】22——多线程基础——FRunnable和FRunnableThread

目录 1、FRunnable 1.1 概念 1.2 主要成员函数 &#xff08;1&#xff09;Init 函数 &#xff08;2&#xff09;Run 函数 &#xff08;3&#xff09;Stop 函数 &#xff08;4&#xff09;Exit 函数 2、FRunnableThread 2.1 概念 2.2 主要操作 &#xff08;1&#xff…

《图解HTTP》 学习日记

1.了解WEB以及网络基础 1.1使用HTTP协议访问WEB web页面显示:根据web浏览器地址栏中输入指定的URL,web浏览器从web服务端获取文件资源(resource)等信息&#xff0c;从而显示出web页面 1.2网络基础TCP/IP 通常使用的网络(包括 互联网)是在tcp/ip协议族的基础上运作的&#xf…

【Docker】docker compose 安装 Redis Stack

注&#xff1a;整理不易&#xff0c;请不要吝啬你的赞和收藏。 前文 Redis Stack 什么是&#xff1f; 简单来说&#xff0c;Redis Stack 是增强版的 Redis &#xff0c;它在传统的 Redis 数据库基础上增加了一些高级功能和模块&#xff0c;以支持更多的使用场景和需求。Redis…

kubesphere前端源码运行

一、下载源码 源码是react&#xff0c;下载地址是 GitHub - kubesphere/console at v3.3.2 然后直接用git下拉就可以了 下拉完成后差不多是这样一个目录结构&#xff0c;记得切分支到3.3.2 二、下载依赖 1、node & yurn 想要运行源码首先需要node&#xff0c;使用刚才…

蓝桥杯历届真题 #分布式队列 (Java,C++)

文章目录 题目解读[蓝桥杯 2024 省 Java B] 分布式队列题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示 思路完整代码 题目解读 题目链接 [蓝桥杯 2024 省 Java B] 分布式队列 题目描述 小蓝最近学习了一种神奇的队列&#xff1a;分布式队列。简单来说&#x…

PySide6 Qt for Python Qt Quick参考网址

Qt QML BOOK&#xff1a; 《Qt for Python》 -Building an Application https://www.qt.io/product/qt6/qml-book/ch19-python-build-app#signals-and-slots Qt for Python&#xff1a;与C版本的差异即BUG处理&#xff08;常见的DLL文件确实的问题等&#xff09; Qt for Pyt…

如何稳定使用 O1 / O1 Pro,让“降智”现象不再困扰?

近期&#xff0c;不少朋友在使用 O1 或 O1 Pro 模型时&#xff0c;都会碰到“降智”或“忽高忽低”的智力波动&#xff0c;比如无法识图、无法生成图片、甚至回答准确度也不稳定。面对这些问题&#xff0c;你是不是也感到头疼呢&#xff1f; 为了找到更可靠的解决办法&#xf…

用户界面的UML建模11

然而&#xff0c;在用户界面方面&#xff0c;重要的是要了解《boundary》类是如何与这个异常分层结构进行关联的。 《exception》类的对象可以作为《control》类的对象。因此&#xff0c;《exception》类能够聚合《boundary》类。 参见图12&#xff0c;《exception》Database…

记录一次面试中被问到的问题 (HR面)

文章目录 一、你对公司的了解多少二、为什么对这个岗位感兴趣三、不能说的离职原因四、离职原因高情商回复五、你的核心优势是什么六、你认为你比其他面试候选人的优势是什么七、不要提及情感 一、你对公司的了解多少 准备要点&#xff1a; 在面试前&#xff0c;对公司进行充分…

前端 图片上鼠标画矩形框,标注文字,任意删除

效果&#xff1a; 页面描述&#xff1a; 对给定的几张图片&#xff0c;每张能用鼠标在图上画框&#xff0c;标注相关文字&#xff0c;框的颜色和文字内容能自定义改变&#xff0c;能删除任意画过的框。 实现思路&#xff1a; 1、对给定的这几张图片&#xff0c;用分页器绑定…

pandas系列----DataFrame简介

DataFrame是Pandas库中最常用的数据结构之一&#xff0c;它是一个类似于二维数组或表格的数据结构。DataFrame由多个列组成&#xff0c;每个列可以是不同的数据类型&#xff08;如整数、浮点数、字符串等&#xff09;。每列都有一个列标签&#xff08;column label&#xff09;…

安装完docker后,如何拉取ubuntu镜像并创建容器?

1. 先docker拉取ubuntu镜像 docker search ubuntu #搜索ubuntu 镜像 docker pull ubuntu:22.04 #拉取ubuntu 镜像 docker images #下载完成后&#xff0c;查看已经下载的镜像 docker run --name ubuntu_container -dit ubuntu:22.04 /bin/bash # docker container -l 2.…

Qt监控系统远程网络登录/请求设备列表/服务器查看实时流/回放视频/验证码请求

一、前言说明 这几个功能是近期定制的功能&#xff0c;也非常具有代表性&#xff0c;核心就是之前登录和设备信息都是在本地&#xff0c;存放在数据库中&#xff0c;数据库可以是本地或者远程的&#xff0c;现在需要改成通过网络API请求的方式&#xff0c;现在很多的服务器很强…

服务器攻击方式有哪几种?

随着互联网的快速发展&#xff0c;网络攻击事件频发&#xff0c;已泛滥成互联网行业的重病&#xff0c;受到了各个行业的关注与重视&#xff0c;因为它对网络安全乃至国家安全都形成了严重的威胁。面对复杂多样的网络攻击&#xff0c;想要有效防御就必须了解网络攻击的相关内容…

Transformer 中缩放点积注意力机制探讨:除以根号 dk 理由及其影响

Transformer 中缩放点积注意力机制的探讨 1. 引言 自2017年Transformer模型被提出以来&#xff0c;它迅速成为自然语言处理&#xff08;NLP&#xff09;领域的主流架构&#xff0c;并在各种任务中取得了卓越的表现。其核心组件之一是注意力机制&#xff0c;尤其是缩放点积注意…

Linux下部署SSM项目

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 Linux部署SSM项目 打包项目 1、修改pom.xml文件&#xff0c;打包方式改为war <packaging>war</packaging>2、idea 通过maven的clean&#xff0c;…

Bytebase 3.0.1 - 可配置在 SQL 编辑器执行 DDL/DML

&#x1f680; 新功能 新增环境策略&#xff0c;允许在 SQL 编辑器内直接执行 DDL/DML 语句。 支持为 BigQuery 数据脱敏。 在项目下新增数据访问控制及脱敏管理页面。 在数据库页面&#xff0c;支持回滚到变更历史的某个版本。 &#x1f514; 兼容性变更 禁止工单创建…

ansible 知识点【回顾梳理】

ansible 知识点 1. 剧本2. facts变量3. register变量4. include功能5. handlers6. when 条件7. with_items 循环8. Jinja2模板9. group_vars10. roles :star::star::star: 看起来字数很多&#xff0c;实际有很多是脚本执行结果&#xff0c;内容不多哦 1. 剧本 剧本很重要的就是…

LLM之RAG实战(五十一)| 使用python和Cypher解析PDF数据,并加载到Neo4j数据库

一、必备条件&#xff1a; python语言Neo4j数据库python库&#xff1a;neo4j、llmsherpa、glob、dotenv 二、代码&#xff1a; from llmsherpa.readers import LayoutPDFReaderfrom neo4j import GraphDatabaseimport uuidimport hashlibimport osimport globfrom datetime …