【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.rhkb.cn/news/1226.html

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

相关文章

Win10微调大语言模型ChatGLM2-6B

在《Win10本地部署大语言模型ChatGLM2-6B-CSDN博客》基础上进行&#xff0c;官方文档在这里&#xff0c;参考了这篇文章 首先确保ChatGLM2-6B下的有ptuning AdvertiseGen下载地址1&#xff0c;地址2&#xff0c;文件中数据留几行 模型文件下载地址 &#xff08;注意&#xff1…

多模态论文笔记——CLIP

大家好&#xff0c;这里是好评笔记&#xff0c;公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细介绍这几年AIGC火爆的隐藏功臣&#xff0c;多模态模型&#xff1a;CLIP。 文章目录 CLIP&#xff08;Contrastive Language-Image Pre-training&#xff09…

【ArcGIS微课1000例】0137:色彩映射表转为RGB全彩模式

本文讲述ArcGIS中,将tif格式的影像数据从色彩映射表转为RGB全彩模式。 参考阅读:【GlobalMapper精品教程】093:将tif影像色彩映射表(调色板)转为RGB全彩模式 文章目录 一、色彩映射表预览二、色彩映射表转为RGB全彩模式一、色彩映射表预览 加载配套数据包中的0137.rar中的…

计算机网络(四)——网络层

目录 一、功能 二、IP数据报分片 三、DHCP动态主机配置协议 四、网络地址转换&#xff08;NAT&#xff09;技术 五、无分类编址CIDR 六、ARP地址解析协议 七、ICMP网际控制报文协议 八、IPv4和IPv6的区别 九、IPv4向IPv6的两种过渡技术——双栈协议和隧道技术 十、路由…

【大数据】机器学习 -----关于data.csv数据集分析案例

打开表 import pandas as pd df2 pd.read_csv("data.csv",encoding"gbk") df2.head()查看数据属性&#xff08;列标题&#xff0c;表形状&#xff0c;类型&#xff0c;行标题&#xff0c;值&#xff09; print("列标题:",df2.columns)Data…

在 Linux 下Ubuntu创建同权限用户

我是因为不小心把最开始创建的用户的文件夹颜色搞没了&#xff0c;再后来全白用习惯了&#xff0c;就不想卸载了&#xff0c;像创建一个和最开始创建的用户有一样的权限可以执行sudo -i进入root一样的用户 如图这是最原始的样子 第一步 创建新用户&#xff0c;我这里是因为之前…

toRef 和 toRefs 详解及应用

在 Vue 3 中&#xff0c;toRef 和 toRefs 是两个用于创建响应式引用的工具&#xff0c;主要用于组合式 API&#xff08;Composition API&#xff09;的场景中 1. toRef 定义 toRef 将某个对象的某个属性包装成一个响应式引用。这样可以直接对该引用进行操作&#xff0c;而不需…

【大模型入门指南 07】量化技术浅析

【大模型入门指南】系列文章&#xff1a; 【大模型入门指南 01】深度学习入门【大模型入门指南 02】LLM大模型基础知识【大模型入门指南 03】提示词工程【大模型入门指南 04】Transformer结构【大模型入门指南 05】LLM技术选型【大模型入门指南 06】LLM数据预处理【大模型入门…

Nginx配置VTS模块-对接Promethues监控

一、背景 Nginx有一个stub_status模块&#xff0c;可以获取到Nginx的一些相关指标。stub_status 模块用于提供基本的 Nginx 性能统计数据。这个模块不是默认编译进 Nginx 的&#xff0c;所以如果需要使用它&#xff0c;确保 Nginx 是带有 --with-http_stub_status_module 选项编…

python 寻找数据拐点

import numpy as np import cv2 from scipy.signal import find_peaks# 示例数据 y_data [365.63258786, 318.34824281, 258.28434505, 228.8913738, 190.87220447, 158.28434505, 129.53035144, 111.95846645, 111.95846645, 120.26517572, 140.71246006, 161.79872204, 180.…

【Leetcode 热题 100】84. 柱状图中最大的矩形

问题背景 给定 n n n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 1 1。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 示例 输入&#xff1a; h e i g h t s [ 2 , 1 , 5 , 6 , 2 , 3 ] heights [2,1…

网络原理(三)—— 传输层 之 UDP 和 TCP协议

传输层 在传输层两大关键的协议就是UDP和TCP协议了&#xff0c;除此之外&#xff0c;还有别的传输层协议&#xff0c;本文章将介绍UDP和TCP协议&#xff0c;重点介绍TCP协议。 首先回顾TCP和UDP 的特点&#xff1a; UDP&#xff1a;不可靠传输&#xff0c;面向数据包&#xf…

MySQL素材怎么导入Navicat???

不管用什么方法都要先关掉MySQL服务&#xff0c;并且提前备份数据&#xff01; 1.有sql文件时候。 打开navicat&#xff0c;运行sql文件 然后点击后面三个点&#xff0c;选中要运行的sql文件&#xff0c;开始。 鼠标右键刷新一下&#xff0c;就能看到sql文件中的表了 2.没有s…

Windows安装ES单机版设置密码

下载ES ES下载链接 我用的是7.17.26 启动前配置 解压之后打开D:\software\elasticsearch-7.17.26\bin\elasticsearch-env.bat 在elasticsearch-env.bat文件中修改jdk的路径 修改前 修改内容 if defined ES_JAVA_HOME (set JAVA"D:\software\elasticsearch-7.17.26\…

mac intel芯片下载安卓模拟器

一、调研 目前主流两个模拟器&#xff1a; 雷神模拟器 不支持macosmumu模拟器pro版 不支持macos intel芯片 搜索到mumu的Q&A中有 “Intel芯片Mac如何安装MuMu&#xff1f;” q&a&#x1f517;&#xff1a;https://mumu.163.com/mac/faq/install-on-intel-mac.html 提…

系统看门狗配置--以ubuntu为例

linux系统配置看门狗 以 ubuntu 系统配置看门狗为例 配置看门狗使用的脚本文件&#xff0c;需要使用管理员权限来执行&#xff1a; 配置是&#xff1a;系统每 30S 喂一次狗&#xff0c;超过 60S 不进行投喂&#xff0c;就会自动重启。 1. 系统脚本内容&#xff1a; #!/bin/b…

每天五分钟深度学习框架pytorch:快速搭建VGG网络的基础模块VGG块

本文重点 前面我们介绍了VGG神经网络,我们知道VGG是由许多的VGG块构成,那么本文我们将使用pytorch搭建VGG块 代码实现: import torch from torch import nn def vgg_block(num_convs,in_channels,out_channels): net=[nn.Conv2d(in_channels,out_channels,kernel_size=3,p…

RocketMQ 知识速览

文章目录 一、消息队列对比二、RocketMQ 基础1. 消息模型2. 技术架构3. 消息类型4. 消费者类型5. 消费者分组和生产者分组 三、RocketMQ 高级1. 如何解决顺序消费和重复消费2. 如何实现分布式事务3. 如何解决消息堆积问题4. 如何保证高性能读写5. 刷盘机制 &#xff08;topic 模…

2_CSS3 背景 --[CSS3 进阶之路]

CSS3 中的背景属性提供了许多强大的功能来增强网页设计&#xff0c;包括但不限于多背景图像、渐变、背景大小控制等。以下是一些关键的 CSS3 背景属性及其用法示例。 1. 多重背景图像 CSS3 允许你为一个元素设置多个背景图像。这些图像按照它们在 background-image 属性中定义…

Kutools for Excel 简体中文版 - 官方正版授权

Kutools for Excel 是一款超棒的 Excel 插件&#xff0c;就像给你的 Excel 加了个超能助手。它有 300 多种实用功能&#xff0c;现在还有 AI 帮忙&#xff0c;能把复杂的任务变简单&#xff0c;重复的事儿也能自动搞定&#xff0c;不管是新手还是老手都能用得顺手。有了它&…