当前位置: 首页 > news >正文

使用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);xy 就行了,且来看看小编是如何来做的:

<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);xy 的变动,很难达到我们想要的效果。

虽然也能通过复杂的计算来得到正确的坐标位置,但是会比较麻烦,小编是个怕麻烦的人,不想一个小需求写太多复杂的东西,这不符合我"程序和人一个能跑就行"的理念。(✪ω✪)

但是,秉着有始有终的原则,还是在网上寻找了很久,还是想看看有没有相关比较完善的算法逻辑过程,可惜无果。

(如果你有比较好的做法,欢迎你在评论给小编分享一下,感谢非常感谢)

那么,这就结束了吗?

当然还没有-.-。

使用 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 用来专门绘制水印,然后把它交给原来 canvasctx.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给上传的整张图片添加平铺的水印

写在开头 哈喽&#xff0c;各位倔友们又见面了&#xff0c;本章我们继续来分享一个实用小技巧&#xff0c;给图片加水印功能&#xff0c;水印功能的目的是为了保护网站或作者版权&#xff0c;防止内容被别人利用或白嫖。 但是网络中&#xff0c;是没有绝对安全的&#xff0c;…...

[安装之4] 联想ThinkPad 加装固态硬盘教程

方案&#xff1a;保留原有的机械硬盘&#xff0c;再加装一个固态硬盘作为系统盘。由于X250没有光驱&#xff0c;这样就无法使用第二个2.5寸的硬盘。还好&#xff0c;X250留有一个M.2接口&#xff0c;这样&#xff0c;就可以使用NGFF M.2接口的固态硬盘。不过&#xff0c;这种接…...

Java数据类型、基本与引用数据类型区别、装箱与拆箱、a=a+b与a+=b区别

文章目录1.Java有哪些数据类型2.Java中引用数据类型有哪些&#xff0c;它们与基本数据类型有什么区别&#xff1f;3.Java中的自动装箱与拆箱4.为什么要有包装类型&#xff1f;5.aab与ab有什么区别吗?1.Java有哪些数据类型 8种基本数据类型&#xff1a; 6种数字类型(4个整数型…...

GoLang设置gofmt和goimports自动格式化

目录 设置gofmt gofmt介绍 配置gofmt 设置goimports goimports介绍 配置goimports 设置gofmt gofmt介绍 Go语言的开发团队制定了统一的官方代码风格&#xff0c;并且推出了 gofmt 工具&#xff08;gofmt 或 go fmt&#xff09;来帮助开发者格式化他们的代码到统一的风格…...

【k8s】如何搭建搭建k8s服务器集群(Kubernetes)

搭建k8s服务器集群 服务器搭建环境随手记 文章目录搭建k8s服务器集群前言&#xff1a;一、前期准备&#xff08;所有节点&#xff09;1.1所有节点&#xff0c;关闭防火墙规则&#xff0c;关闭selinux&#xff0c;关闭swap交换&#xff0c;打通所有服务器网络&#xff0c;进行p…...

DIDL4_前向传播与反向传播(模型参数的更新)

前向传播与反向传播前向传播与反向传播的作用前向传播及公式前向传播范例反向传播及公式反向传播范例小结前向传播计算图前向传播与反向传播的作用 在训练神经网络时&#xff0c;前向传播和反向传播相互依赖。 对于前向传播&#xff0c;我们沿着依赖的方向遍历计算图并计算其路…...

链表学习之链表划分

链表解题技巧 额外的数据结构&#xff08;哈希表&#xff09;&#xff1b;快慢指针&#xff1b;虚拟头节点&#xff1b; 链表划分 将单向链表值划分为左边小、中间相等、右边大的形式。中间值为pivot划分值。 要求&#xff1a;调整之后节点的相对次序不变&#xff0c;时间复…...

(考研湖科大教书匠计算机网络)第五章传输层-第一、二节:传输层概述及端口号、复用分用等概念

获取pdf&#xff1a;密码7281专栏目录首页&#xff1a;【专栏必读】考研湖科大教书匠计算机网络笔记导航 文章目录一&#xff1a;传输层概述&#xff08;1&#xff09;概述&#xff08;2&#xff09;从计算机网络体系结构角度看传输层&#xff08;3&#xff09;传输层意义二&am…...

C#:Krypton控件使用方法详解(第七讲) ——kryptonHeader

今天介绍的Krypton控件中的kryptonHeader&#xff0c;下面开始介绍这个控件的属性&#xff1a;控件的样子如上图所示&#xff0c;从上面控件外观来看&#xff0c;这个控件有三部分组成。第一部分是前面的图片&#xff0c;第二部分是kryptonHeader1文本&#xff0c;第三部分是控…...

5年软件测试工程师分享的自动化测试经验,一定要看

今天给大家分享一个华为的软件测试工程师分享的关于自动化测试的经验及干货。真的后悔太晚找他要了&#xff0c; 纯干货。一定要看完&#xff01; 1.什么是自动化测试&#xff1f; 用程序测试程序&#xff0c;用代码取代思考&#xff0c;用脚本运行取代手工测试。自动化测试涵…...

什么是猜疑心理?小猫测试网科普小作文

什么是猜疑心理&#xff1f;猜疑心理是说一个人心中想法偏离了客观事实&#xff0c;牵强附会&#xff0c;往往是指不好的一面&#xff0c;对别人的一言一行都充满了不良的解读&#xff0c;认为这些对自己都有针对性&#xff0c;目的性&#xff0c;对自己都是不利的。猜疑心理重…...

Redis命令行对常用数据结构String、list、set、zset、hash等增删改查操作

1.Redis命令的小套路 - NX&#xff1a;not exist - EX&#xff1a;expire - M&#xff1a;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卡快速扩容参考&#xff1a;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等

上篇&#xff1a;JVM学习总结&#xff0c;全面介绍运行时数据区域、各类垃圾收集器的原理使用、内存分配回收策略 参考资料&#xff1a;《深入理解Java虚拟机》第三版 文章目录三&#xff0c;虚拟机性能监控、故障处理工具1&#xff09;jps&#xff1a;虚拟机进程状况工具2&…...

指针笔记(指针数组和指向数组的指针,数组中a和a的区别等)

指针数组和指向数组的指针 int *p[4]和int (*p)[4]有何区别&#xff1f; 前者是一个指针数组&#xff0c;数组大小为4&#xff0c;每一个元素都是一个指向int的指针 后者是指向int[4]类型数组的指针 以上代码若运行会报如下错误 main函数中定义的a数组本质是一个指向int[2]的…...

MySQL ---基础概念

目录 餐前小饮&#xff1a;什么是服务器&#xff1f;什么是数据库服务器&#xff1f; 一、数据库服务软件 1. 常见数据库产品 2.如何开启和停止MySQL服务 二、数据库术语及语法 1.数据库术语 2.SQL语法结构 3.SQL 语法要点 三、SQL分类 1.数据定义语言&#xff08;D…...

【基础】Flink -- ProcessFunction

Flink -- ProcessFunction处理函数概述处理函数基本处理函数 ProcessFunction按键分区处理函数 KeyedProcessFunction定时器与定时服务基于处理时间的分区处理函数基于事件时间的分区处理函数窗口处理函数 ProcessWindowFunction应用案例 -- Top N处理函数概述 为了使代码拥有…...

JavaEE|网络编程基础与Socket套接字

文章目录一、为什么需要网络编程二、什么是网络编程三、网络编程中的基本概念1.发送端和接收端2.请求和响应3.客户端和服务端4.常见的客户端服务端模型四、Socket套接字概念及分类1.概念2.分类1&#xff09;流套接字&#xff1a;使用传输层TCP协议2&#xff09;数据报套接字&am…...

【SpringBoot】基础协议及邮件配置整合

一、名词概念解释 什么是POP3、SMTP和IMAP&#xff1f; 简单的说&#xff1a;POP3和IMAP是用来从服务器上下载邮件的。SMTP适用于发送或中转信件时找到下一个目的地。所以我们发送邮件应该使用SMTP协议。 POP3、SMTP和IMAP协议介绍 IMAP和POP3有什么区别&#xff1f;什么是免费…...

pytorch配置—什么是CUDA,什么是CUDNN、在配置pytorch虚拟环境中遇到的问题、在安装gpu—pytorch中遇到的问题

1.什么是CUDA&#xff0c;什么是CUDNN &#xff08;1&#xff09;什么是CUDA CUDA(ComputeUnified Device Architecture)&#xff0c;是显卡厂商NVIDIA推出的运算平台。 CUDA是一种由NVIDIA推出的通用并行计算架构&#xff0c;该架构使GPU能够解决复杂的计算问题。 &#xff0…...

jfr引起的一次jvm异常记录

业务生产启动时&#xff0c;20个节点有1-2个节点因为jvm问题出现启动失败&#xff0c;k8s自动重启后正常。在测试环境2个节点下偶现 排查思路&#xff1a; 先拿到hs_err_pid的jvm错误文件找到当前线程和内部错误信息 hs_err_pid 文件分析 当前线程&#xff1a;lettuce的线程…...

Java智慧校园平台源码:SaaS模式智慧校园运营云平台源码

校班务管理&#xff1a;评价管理&#xff1a; 1.web端/教师端小程序编辑点评 多元化评价&#xff0c;捕捉学生闪光点全方位评价&#xff0c;自定义评价类型、 评价信息实时推送至家长、AI智能点评 班级报表一键导出&#xff0c;智能评测学生在校表现&#xff0c;老师、家长实…...

【yolov5】将标注好的数据集进行划分(附完整可运行python代码)

问题描述 准备使用yolov5训练自己的模型&#xff0c;自己将下载的开源数据集按照自己的要求重新标注了一下&#xff0c;然后现在对其进行划分。 问题分析 划分数据集主要的步骤就是&#xff0c;首先要将数据集打乱顺序&#xff0c;然后按照一定的比例将其分为训练集&#xf…...

es-05分词器

文章目录分词器1 normalization&#xff1a;文档规范化,提高召回率2 字符过滤器&#xff08;character filter&#xff09;&#xff1a;分词之前的预处理&#xff0c;过滤无用字符3 令牌过滤器&#xff08;token filter&#xff09;&#xff1a;停用词、时态转换、大小写转换、…...

已解决zipfile.BadZipFile: File is not a zip file

已解决Python openpyxl 读取Excel文件&#xff0c;抛出异常zipfile.BadZipFile: File is not a zip file的正确解决&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 文章目录报错问题报错翻译报错原因解决方法联系博主免费帮忙解决报错报错问题 一个小伙伴遇到问题跑…...

Mybatis源码分析:Mybatis的数据存储对象

前言&#xff1a;SQLSession是对JDBC的封装 一&#xff1a;SQLSession和JDBC的对照说明 左边是我们的客户端程序&#xff0c;右边是我们的MySQL数据仓&#xff0c;或者叫MySQL实例 Mybatis是对JDBC的封装&#xff0c;将JDBC封装成了一个核心的SQLSession对象 JDBC当中的核心对…...

学习 Python 之 Pygame 开发坦克大战(二)

学习 Python 之 Pygame 开发坦克大战&#xff08;二&#xff09;坦克大战的需求开始编写坦克大战1. 搭建主类框架2. 获取窗口中的事件3. 创建基类4. 初始化我方坦克类5. 完善我方坦克的移动5. 完善我方坦克的显示6. 在主类中加入我方坦克并完成坦克移动7. 初始化子弹类8. 完善子…...

短视频时代是靠什么赚钱的,介绍常见的5种方式,简单明了

目前&#xff0c;短视频越来越火热&#xff0c;大家都知道做短视频可以赚钱&#xff0c;那么究竟是靠什么赚钱的&#xff0c;又有几个人知道呢&#xff1f;短视频创业有个人、有团队&#xff0c;怎么实现团队的生存和发展。 常见的几种变现方式有&#xff1a; 1、平台分成 各…...

关于CentOS维护的几条简单命令

1、检查/etc/passwd这个文件里面有没有异常用户名2、通过命令top查看是否有异常进程&#xff0c;按M键对进程进行排序3、通过命令netstat -lnpt&#xff0c;查看是否有异常端口号4、通过命令ll -a /proc/PID&#xff0c;查看异常进程执行文件所在位置5、通过命令kill -9 PID&am…...

PoW 、PoS , DPoS 算法

PoW 、PoS &#xff0c; DPoS 算法 在区块链领域&#xff0c;多采用 PoW 工作量证明算法、PoS 权益证明算法&#xff0c;以及 DPoS 代理权 益证明算法&#xff0c;以上三种是业界主流的共识算法&#xff0c;这些算法与经典分布式一致性算法不同的是 融入了经济学博弈的概念。 …...

政府网站cms模板/网络黄页推广软件哪个好

本篇文章主要给大家介绍关于css实现图片的滑动效果示例&#xff0c;希望大家阅读完本篇后对css滑动效果的相关知识有所了解。下面给大家举一个简单的css图片滑动效果示例(自下而上滑动)&#xff1a;css怎么实现图片滑动img{height: 200px;width: 200px;}.slider {overflow-y: h…...

深圳沙井做公司网站/网站制作企业

运算符重载 和 &#xff1a;连接字符串 &#xff1a;字符串赋值 >、>、< 和 <&#xff1a;字符串比较&#xff08;例如a < b, aa < ab&#xff09; 、!&#xff1a;比较字符串 <<、>>&#xff1a;输出、输入字符串 注意&#xff1a;使用重载的运…...

静态网站和动态网站/seo营销

建表语句点击详见 – 查询男生、女生人数 SELECTst.s_sex, COUNT(1) FROM student st GROUP BY st.s_sex...

宜城营销型网站套餐/seo优化行业

现在大部分人家里都会养些宠物&#xff0c;比如猫猫狗狗的&#xff0c;狗子很可爱&#xff0c;拆家很上头&#xff0c; 养狗有三怕&#xff1a;拆家、乱拉屎、整夜叫。虽然狗狗的一些行为习惯跟与遗传有关&#xff0c; 但是主人后天的影响也会给狗狗带来改变&#xff0c;因此…...

wordpress会员发布文章/上海公司排名

设置定时任务很简单&#xff0c;用Timer类就搞定了。一、延时执行首先&#xff0c;我们定义一个类&#xff0c;给它取个名字叫TimeTask&#xff0c;我们的定时任务&#xff0c;就在这个类的main函数里执行。代码如下&#xff1a;package test;import java.util.Timer;public cl…...

做网站送的企业邮箱能用吗/国内永久免费域名注册

1.面向过程的思维方式&#xff1a; 就是把解决问题的关注点&#xff0c;放到解决问题的每一个详细的步骤上面&#xff01; 面向对象的思维方式&#xff1a;&#xff08;面向对象就是一种思维方式&#xff0c;与代码关系不大&#xff09; 就是把解决问题的关注点&#xff0c;放到…...