使用canvas给上传的整张图片添加平铺的水印
写在开头
哈喽,各位倔友们又见面了,本章我们继续来分享一个实用小技巧,给图片加水印功能,水印功能的目的是为了保护网站或作者版权,防止内容被别人利用或白嫖。
但是网络中,是没有绝对安全的,我们只能尽可能去完善安全机制,像水印功能也只能是防君子,防不了小人。
下面小编画了一张添加水印的简易流程图:
绘制图片
接下来,进入文章主题,既然是要给图片添加水印,那么我们先来把图片绘制到 canvas 上,具体如下:
<template><div><input type="file" @change="upload" /><br /><br /><canvas id="canvas" /></div>
</template><script> export default {methods: {upload(e) {const file = e.target.files[0];if (<img src="http://localhost:8081/0cd115e2-9d4a-4c67-a86b-77e84d6f61dbconst img = new Image();img.src = filePath;img.onload = () => {this.addWaterMark(img);}},addWaterMark(img) {// const canvas = document.createElement('canvas');const canvas = document.getElementById('canvas');const imgWidth = img.width;const imgHeight = img.height;canvas.width = imgWidth;canvas.height = imgHeight;const ctx = canvas.getContext('2d');ctx.drawImage(img, 0, 0); // 绘制图片}}" style="margin: auto" />
}; </script><style scoped> #canvas {border: 1px solid red;
} </style>
整体代码不难,为了方便演示,小编直接把 canvas 放在 template 中,但真实使用场景下你可以使用 document.createElement('canvas') 来创建 Dom 并在使用结束后删除相关 DOM,这样才是比较好的方式唷。(✪ω✪)
还有就是使用 ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) API 来绘制图片。
作为一名前端人员小编希望你对 canvas 多多少少要有一点了解哦,不能说精通,但是基础知识咱们要掌握哦。
绘制水印
把图片绘制到 canvas 后,接下来我们来把水印也给整上。
<script>
export default {methods: {upload(e) { ... },addWaterMark(img) {const canvas = document.getElementById('canvas');const imgWidth = img.width;const imgHeight = img.height;canvas.width = imgWidth;canvas.height = imgHeight;const ctx = canvas.getContext('2d');ctx.drawImage(img, 0, 0);// 画笔样式ctx.textAlign = 'left';ctx.textBaseline = 'top';ctx.font = '12px Microsoft Yahei';ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';ctx.fillText('橙某人', 0, 0);ctx.fillText('2022年11月22日 09:22:30', 0, 20);}},
};
</script>
上图左上角能看到我们很简单就把水印加上了,当然,这还达不到产品经理的要求,我们需要把水印平铺开来,防止别人轻易通过截图就把水印清除了。
绘制平铺水印
而这个平铺过程也很简单,只要循环去改变 ctx.fillText(text, x, y); 的 x 和 y 就行了,且来看看小编是如何来做的:
<script>
export default {methods: {upload(e) { ... },addWaterMark(img) {const canvas = document.getElementById('canvas');const imgWidth = img.width;const imgHeight = img.height;canvas.width = imgWidth;canvas.height = imgHeight;const ctx = canvas.getContext('2d');ctx.drawImage(img, 0, 0);// 画笔样式ctx.textAlign = 'left';ctx.textBaseline = 'top';ctx.font = '12px Microsoft Yahei';ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';// ctx.fillText('橙某人', 0, 0);// ctx.fillText('2022年11月22日 09:22:30', 0, 20);// 平铺水印const name = '橙某人';const date = '2022年11月22日 09:22:30';const height = 120;const width = 200;let i = 0;let j = 0;const waterMarkerWidth = ctx.measureText(name).width;ctx.rotate(-20 * Math.PI / 180);for (i = 0; i <= imgWidth / (waterMarkerWidth) + 100; i++) {for (j = 0; j <= imgHeight / (height - 20) + 100; j++) {const x = i * (waterMarkerWidth + width) - 100;if (j === 0) {ctx.fillText(name, x, -height, imgWidth);ctx.fillText(date, x, -height + 20, imgWidth);}ctx.fillText(name, x, j * height, imgWidth);ctx.fillText(date, x, j * height + 20, imgWidth);}}}},
};
</script>
我们先不细看代码具体细节过程,上面小编放了两张图片,图中可以看出,水印是平铺开来了,但是效果可能有点差强人意。因为这里在绘制过程中需要考虑的因素比较多,比如图片大小、水印文字大小、长短、间隔、旋转角度等等。
特别是 ctx.rotate(deg); 旋转角度是比较麻烦的,它是将整个画布进行(canvas)旋转的,我们要旋转水印,也只能通过该 API 来实现。但是由于是整个画布的旋转,这会造成 ctx.fillText(text, x, y); 的 x 和 y 的变动,很难达到我们想要的效果。
虽然也能通过复杂的计算来得到正确的坐标位置,但是会比较麻烦,小编是个怕麻烦的人,不想一个小需求写太多复杂的东西,这不符合我"程序和人一个能跑就行"的理念。(✪ω✪)
但是,秉着有始有终的原则,还是在网上寻找了很久,还是想看看有没有相关比较完善的算法逻辑过程,可惜无果。
(如果你有比较好的做法,欢迎你在评论给小编分享一下,感谢非常感谢)
那么,这就结束了吗?
当然还没有-.-。
使用 ctx.createPattern 绘制平铺水印
经小编摸鱼得知,canvas 还有一个 ctx.createPattern API 可以用于绘制重复的内容,就和背景图片的 background-repeat: 'repeat' 属性效果一样。
那么这不就简单多了,还瞎整啥,且继续来看代码:
<script>
export default {methods: {upload(e) { ... },addWaterMark(img) {const canvas = document.getElementById('canvas');const imgWidth = img.width;const imgHeight = img.height;canvas.width = imgWidth;canvas.height = imgHeight;const ctx = canvas.getContext('2d');ctx.drawImage(img, 0, 0);// 平铺水印const canvasWater = document.createElement('canvas');const waterMarkSize = 200; // 水印大小canvasWater.width = waterMarkSize;canvasWater.height = waterMarkSize;const ctxWater = canvasWater.getContext('2d');ctxWater.textAlign = 'left';ctxWater.textBaseline = 'top';ctxWater.font = '12px Microsoft Yahei';ctxWater.fillStyle = 'rgba(255, 255, 255, 0.3)';ctxWater.rotate(-20 * Math.PI/180);ctxWater.fillText('橙某人', 60, 80);ctxWater.fillText('2022年11月22日 09:22:30', 10, 100);ctx.fillStyle = ctx.createPattern(canvasWater, 'repeat'); // 绘制重复的水印ctx.fillRect(0, 0, canvas.width, canvas.height);}},
};
上面我们通过创建一个新的 canvas 用来专门绘制水印,然后把它交给原来 canvas 的 ctx.createPattern() 方法,该方法可以接收七种类型的参数,然后重复绘制出来。
利用这种方式来平铺水印,相比上一种方式,就比较简单一些了,是人能看得懂的代码了,也能满足产品需求了。
输出文件
最后,我们把 canvas 再转换 file 对象就大功告成了。
<script>
export default {methods: {upload(e) { const file = e.target.files[0];if (!file) return;const filePath = window.URL.createObjectURL(file); const img = new Image();img.src = filePath;img.onload = () => {const newFile = this.addWaterMark(img, file.name);console.log(newFile);}},addWaterMark(img, fileName) {const canvas = document.getElementById('canvas');const imgWidth = img.width;const imgHeight = img.height;canvas.width = imgWidth;canvas.height = imgHeight;const ctx = canvas.getContext('2d');ctx.drawImage(img, 0, 0);// 平铺水印const canvasWater = document.createElement('canvas');const waterMarkSize = 200; // 水印大小canvasWater.width = waterMarkSize;canvasWater.height = waterMarkSize;const ctxWater = canvasWater.getContext('2d');ctxWater.textAlign = 'left';ctxWater.textBaseline = 'top';ctxWater.font = '12px Microsoft Yahei';ctxWater.fillStyle = 'rgba(255, 255, 255, 0.3)';ctxWater.rotate(-20 * Math.PI/180);ctxWater.fillText('橙某人', 60, 80);ctxWater.fillText('2022年11月22日 09:22:30', 10, 100);ctx.fillStyle = ctx.createPattern(canvasWater, 'repeat'); ctx.fillRect(0, 0, canvas.width, canvas.height);const base64 = canvas.toDataURL('image/jpeg', 0.8)return this.dataURLtoBlob(base64, fileName)}},// base64转文件对象dataURLtoBlob(dataurl, name) {const arr = dataurl.split(',')const mime = arr[0].match(/:(.*?);/)[1]const bstr = atob(arr[1])let n = bstr.lengthconst u8arr = new Uint8Array(n)while (n--) {u8arr[n] = bstr.charCodeAt(n)}return new File([u8arr], name, {type: mime})}
};
最后
为大家准备了一个前端资料包。包含54本,2.57G的前端相关电子书,《前端面试宝典(附答案和解析)》,难点、重点知识视频教程(全套)。



有需要的小伙伴,可以点击下方卡片领取,无偿分享
相关文章:
使用canvas给上传的整张图片添加平铺的水印
写在开头 哈喽,各位倔友们又见面了,本章我们继续来分享一个实用小技巧,给图片加水印功能,水印功能的目的是为了保护网站或作者版权,防止内容被别人利用或白嫖。 但是网络中,是没有绝对安全的,…...
[安装之4] 联想ThinkPad 加装固态硬盘教程
方案:保留原有的机械硬盘,再加装一个固态硬盘作为系统盘。由于X250没有光驱,这样就无法使用第二个2.5寸的硬盘。还好,X250留有一个M.2接口,这样,就可以使用NGFF M.2接口的固态硬盘。不过,这种接…...
Java数据类型、基本与引用数据类型区别、装箱与拆箱、a=a+b与a+=b区别
文章目录1.Java有哪些数据类型2.Java中引用数据类型有哪些,它们与基本数据类型有什么区别?3.Java中的自动装箱与拆箱4.为什么要有包装类型?5.aab与ab有什么区别吗?1.Java有哪些数据类型 8种基本数据类型: 6种数字类型(4个整数型…...
GoLang设置gofmt和goimports自动格式化
目录 设置gofmt gofmt介绍 配置gofmt 设置goimports goimports介绍 配置goimports 设置gofmt gofmt介绍 Go语言的开发团队制定了统一的官方代码风格,并且推出了 gofmt 工具(gofmt 或 go fmt)来帮助开发者格式化他们的代码到统一的风格…...
【k8s】如何搭建搭建k8s服务器集群(Kubernetes)
搭建k8s服务器集群 服务器搭建环境随手记 文章目录搭建k8s服务器集群前言:一、前期准备(所有节点)1.1所有节点,关闭防火墙规则,关闭selinux,关闭swap交换,打通所有服务器网络,进行p…...
DIDL4_前向传播与反向传播(模型参数的更新)
前向传播与反向传播前向传播与反向传播的作用前向传播及公式前向传播范例反向传播及公式反向传播范例小结前向传播计算图前向传播与反向传播的作用 在训练神经网络时,前向传播和反向传播相互依赖。 对于前向传播,我们沿着依赖的方向遍历计算图并计算其路…...
链表学习之链表划分
链表解题技巧 额外的数据结构(哈希表);快慢指针;虚拟头节点; 链表划分 将单向链表值划分为左边小、中间相等、右边大的形式。中间值为pivot划分值。 要求:调整之后节点的相对次序不变,时间复…...
(考研湖科大教书匠计算机网络)第五章传输层-第一、二节:传输层概述及端口号、复用分用等概念
获取pdf:密码7281专栏目录首页:【专栏必读】考研湖科大教书匠计算机网络笔记导航 文章目录一:传输层概述(1)概述(2)从计算机网络体系结构角度看传输层(3)传输层意义二&am…...
C#:Krypton控件使用方法详解(第七讲) ——kryptonHeader
今天介绍的Krypton控件中的kryptonHeader,下面开始介绍这个控件的属性:控件的样子如上图所示,从上面控件外观来看,这个控件有三部分组成。第一部分是前面的图片,第二部分是kryptonHeader1文本,第三部分是控…...
5年软件测试工程师分享的自动化测试经验,一定要看
今天给大家分享一个华为的软件测试工程师分享的关于自动化测试的经验及干货。真的后悔太晚找他要了, 纯干货。一定要看完! 1.什么是自动化测试? 用程序测试程序,用代码取代思考,用脚本运行取代手工测试。自动化测试涵…...
什么是猜疑心理?小猫测试网科普小作文
什么是猜疑心理?猜疑心理是说一个人心中想法偏离了客观事实,牵强附会,往往是指不好的一面,对别人的一言一行都充满了不良的解读,认为这些对自己都有针对性,目的性,对自己都是不利的。猜疑心理重…...
Redis命令行对常用数据结构String、list、set、zset、hash等增删改查操作
1.Redis命令的小套路 - NX:not exist - EX:expire - M:multi 2.基本操作 ①切换数据库 Redis默认有16个数据库。 115 # Set the number of databases. The default database is DB 0, you can select 116 # a different one on a per-con…...
mycobot 使用教程
(1) 树莓派4B ubuntu系统调整swap空间与使SD卡快速扩容参考:https://www.bilibili.com/read/cv14825069https://blog.csdn.net/weixin_45824920/article/details/114381292?spm1001.2101.3001.6650.1&utm_mediumdistribute.pc_relevant.none-task-blog-2%7Edef…...
JVM学习总结,虚拟机性能监控、故障处理工具:jps、jstat、jinfo、jmap、Visual VM、jstack等
上篇:JVM学习总结,全面介绍运行时数据区域、各类垃圾收集器的原理使用、内存分配回收策略 参考资料:《深入理解Java虚拟机》第三版 文章目录三,虚拟机性能监控、故障处理工具1)jps:虚拟机进程状况工具2&…...
指针笔记(指针数组和指向数组的指针,数组中a和a的区别等)
指针数组和指向数组的指针 int *p[4]和int (*p)[4]有何区别? 前者是一个指针数组,数组大小为4,每一个元素都是一个指向int的指针 后者是指向int[4]类型数组的指针 以上代码若运行会报如下错误 main函数中定义的a数组本质是一个指向int[2]的…...
MySQL ---基础概念
目录 餐前小饮:什么是服务器?什么是数据库服务器? 一、数据库服务软件 1. 常见数据库产品 2.如何开启和停止MySQL服务 二、数据库术语及语法 1.数据库术语 2.SQL语法结构 3.SQL 语法要点 三、SQL分类 1.数据定义语言(D…...
【基础】Flink -- ProcessFunction
Flink -- ProcessFunction处理函数概述处理函数基本处理函数 ProcessFunction按键分区处理函数 KeyedProcessFunction定时器与定时服务基于处理时间的分区处理函数基于事件时间的分区处理函数窗口处理函数 ProcessWindowFunction应用案例 -- Top N处理函数概述 为了使代码拥有…...
JavaEE|网络编程基础与Socket套接字
文章目录一、为什么需要网络编程二、什么是网络编程三、网络编程中的基本概念1.发送端和接收端2.请求和响应3.客户端和服务端4.常见的客户端服务端模型四、Socket套接字概念及分类1.概念2.分类1)流套接字:使用传输层TCP协议2)数据报套接字&am…...
【SpringBoot】基础协议及邮件配置整合
一、名词概念解释 什么是POP3、SMTP和IMAP? 简单的说:POP3和IMAP是用来从服务器上下载邮件的。SMTP适用于发送或中转信件时找到下一个目的地。所以我们发送邮件应该使用SMTP协议。 POP3、SMTP和IMAP协议介绍 IMAP和POP3有什么区别?什么是免费…...
pytorch配置—什么是CUDA,什么是CUDNN、在配置pytorch虚拟环境中遇到的问题、在安装gpu—pytorch中遇到的问题
1.什么是CUDA,什么是CUDNN (1)什么是CUDA CUDA(ComputeUnified Device Architecture),是显卡厂商NVIDIA推出的运算平台。 CUDA是一种由NVIDIA推出的通用并行计算架构,该架构使GPU能够解决复杂的计算问题。 ࿰…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...
pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...
Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)
引言 在人工智能飞速发展的今天,大语言模型(Large Language Models, LLMs)已成为技术领域的焦点。从智能写作到代码生成,LLM 的应用场景不断扩展,深刻改变了我们的工作和生活方式。然而,理解这些模型的内部…...
论文阅读笔记——Muffin: Testing Deep Learning Libraries via Neural Architecture Fuzzing
Muffin 论文 现有方法 CRADLE 和 LEMON,依赖模型推理阶段输出进行差分测试,但在训练阶段是不可行的,因为训练阶段直到最后才有固定输出,中间过程是不断变化的。API 库覆盖低,因为各个 API 都是在各种具体场景下使用。…...
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...
