使用canvas给上传的整张图片添加平铺的水印

news2024/9/24 15:23:43

写在开头

哈喽,各位倔友们又见面了,本章我们继续来分享一个实用小技巧,给图片加水印功能,水印功能的目的是为了保护网站或作者版权,防止内容被别人利用或白嫖。

但是网络中,是没有绝对安全的,我们只能尽可能去完善安全机制,像水印功能也只能是防君子,防不了小人。

下面小编画了一张添加水印的简易流程图:

绘制图片

接下来,进入文章主题,既然是要给图片添加水印,那么我们先来把图片绘制到 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的前端相关电子书,《前端面试宝典(附答案和解析)》,难点、重点知识视频教程(全套)。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

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

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

相关文章

iOS接入Google登录

1.在Google Cloud后台配置客户端ID 首先要在 Google Cloud 中创建一个项目。新创建的Project需要先配置同意屏幕。一共有4步骤需要配置。 1.OAuth 同意屏幕 User Type选择"外部"进行创建。填写必必要的信息&#xff0c;应用名称、用户支持电子邮件地址、开发者电子邮…

chatGPT是什么?chatGPT运用场景有哪些?

大家好&#xff0c;小编来为大家解答以下问题 chatGPT是什么&#xff1f;&#xff0c;chatGPT概念股有哪些&#xff1f;一个有趣的事情&#xff0c;一个有趣的事情&#xff0c;现在让我们一起来看看吧&#xff01; 1、chatpgt是什么 ChatGPT是OpenAI开发的大型预训练语言模型。…

yolov5 onnx 前后处理+运行推理(暂记)

代码在这个基础上改的&#xff0c;虽然跑通了&#xff0c;还是很混乱&#xff0c;这里先简单记录一下处理的流程&#xff1a; yolov5 环境设置yolov5 网络结构ONNX yolov5导出 convert error --grid番外&#xff1a;onnx直接操作番外&#xff1a;yolov5的重新训练 result 0 -…

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

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

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

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

C语言中用rand()函数产生一随机数

在C语言中如何产生一个随机数呢&#xff1f;用rand()函数。 rand()函数在头文件&#xff1a;#include <stdio.h>中&#xff0c;函数原型&#xff1a;int rand(void);。rand()会返回一个范围在0到RAND_MAX&#xff08;32767&#xff09;之间的随机数&#xff08;整数&…

Pytorch 基础之张量数据类型

学习之前&#xff1a;先了解 Tensor&#xff08;张量&#xff09; 官方文档的解释是&#xff1a; 张量如同数组和矩阵一样, 是一种特殊的数据结构。在PyTorch中, 神经网络的输入、输出以及网络的参数等数据, 都是使用张量来进行描述。 说白了就是一种数据结构 基本数据类型…

Python可以解码吗,解码打码是如何实现的

前言 咳咳&#xff0c;进来的铁汁都是抱着学习的心态进来看的吧&#xff0c;咱今天不讲解解码&#xff0c;咱来说说python如何来实现打码功能~ 这一个个进来的 都是标题党吧哈哈哈 有兴趣的可以继续看看哦 最近重温了一档综艺节目 至于叫什么 这里就不细说了 老是看着看着就…

【QT 5 相关实验-示波器-学习笔记-示波器组件练习与使用总结】

【QT 5 相关实验-示波器-学习笔记-示波器组件练习与使用总结】1、概述2、实验环境3、参考资料-致谢4、自我提升实验效果视频演示5、代码练习-学习后拆解-实验步骤&#xff08;1&#xff09;头文件部分-"mwaveview.h"&#xff08;2&#xff09;cpp文件部分-"mwav…

UDP数据报套接字编程

DatagramSocket API DatagramSocket 是UDP Socket&#xff0c;用于发送和接收UDP数据报。 DatagramSocket 构造方法&#xff1a; DatagramSocket 方法&#xff1a; DatagramPacket API DatagramPacket是UDP Socket发送和接收的数据报。 DatagramPacket 构造方法&#xff…

本地生成动漫风格 AI 绘画 图像|Stable Diffusion WebUI 的安装和局域网部署教程

Stable Diffusion WebUI 的安装和部署教程1. 简介2. 安装环境2.1 Windows2.2 Linux3. 运行4. 模型下载链接5. 局域网部署5.1 Windows5.2 Linux6. 其他资源1. 简介 先放一张WebUI的图片生成效果图&#xff0c;以给大家学习的动力 &#xff1a;&#xff09; 怎么样&#xff0c;…

浅析SAS协议(1):基本介绍

文章目录概述SAS协议发展历程SAS技术特性SAS设备拓扑SAS phySAS地址SAS设备类型SAS协议分层参考链接概述 SAS&#xff0c;全称Serial Attached SCSI&#xff0c;即串行连结SCSI&#xff0c;是一种采用了串行总线的高速互连技术。通过物理上使用串行总线连结&#xff0c;在链路…

用一行Python代码,为图片上水印版权!

今天一个朋友跟我吐槽&#xff1a;前段时间&#xff0c;我辛辛苦苦整理的一份XX攻略&#xff0c;分享给自己的一些朋友&#xff0c;结果今天看到有人堂而皇之地拿着这份攻略图片去引流&#xff0c;并声称是自己整理的&#xff0c;真是岂有此理&#xff01;他自己总结吃一堑长一…

超低成本DDoS攻击来袭,看WAF如何绝地防护

一、DDoS攻击&#xff0c;不止于网络传输层 网络世界里为人们所熟知的DDoS攻击&#xff0c;多数是通过对带宽或网络计算资源的持续、大量消耗&#xff0c;最终导致目标网络与业务的瘫痪&#xff1b;这类DDOS攻击&#xff0c; 工作在OSI模型的网络层与传输层&#xff0c;利用协…

【MyBatis】源码学习 03 - 类型处理器 TypeHandler

文章目录前言参考目录学习笔记1、type 包中类的归类总结2、类型处理器2.1、TypeReference 类3、类型注册表3.1、TypeHandlerRegistry#getTypeHandler前言 本文内容对应的是书本第 8 章的内容&#xff0c;主要是关于类型处理器 TypeHandler 的学习。 这一章节的学习有些地方理…

Java爬虫——WebMagic案例

抓取51Job的招聘信息一&#xff0c; Scheduler组件在解析页面的时候&#xff0c;很可能会解析出相同的url地址(例如商品标题和商品图片超链接&#xff0c;而且url一样)&#xff0c;如果不进行处理&#xff0c;同样的url会解析处理多次&#xff0c;浪费资源。所以我们需要有一个…

【Unity3d】Unity与Android之间通信

在unity开发或者sdk开发经常遇到unity与移动端原生层之间进行通信&#xff0c;这里把它们之间通信做一个整理。 关于Unity与iOS之间通信&#xff0c;参考【Unity3d】Unity与iOS之间通信 Unity(c#)调用Android (一)、编写Java代码 实际上&#xff0c;任何已经存在的Java代码…

【webpack5】一些常见优化配置及原理介绍(二)

这里写目录标题介绍sourcemap定位报错热模块替换&#xff08;或热替换&#xff0c;HMR&#xff09;oneOf精准解析指定或排除编译开启缓存多进程打包移除未引用代码配置babel&#xff0c;减小代码体积代码分割&#xff08;Code Split&#xff09;介绍预获取/预加载(prefetch/pre…

HydroD 实用教程(四)水动力模型

目 录一、前言二、Hydro Properties2.1 Compartment Properties2.2 Rudder and Thruster2.3 Wind Properties三、Hydro Structure3.1 Load Cross Sections四、Loading Conditions4.1 Mass Model4.2 Second Order Surface Model4.3 Wadam Offbody Points4.4 Additional Matrices…

Redis的常见操作和Session的持久化

安装Redis使用yum命令&#xff0c;直接将redis安装到linux服务器&#xff1a;yum -y install redis启动redis使用以下命令&#xff0c;以后台运行方式启动redis&#xff1a;redis -server /etc/redis.conf &操作redis使用以下命令启动redis客户端&#xff1a;redis-cli设置…