theme: cyanosis
本文正在参加「金石计划 . 瓜分6万现金大奖」
写在开头
哈喽,各位倔友们又见面了,本章我们继续来分享一个实用小技巧,给图片加水印功能,水印功能的目的是为了保护网站或作者版权,防止内容被别人利用或白嫖。
但是网络中,是没有绝对安全的,我们只能尽可能去完善安全机制,像水印功能也只能是防君子,防不了小人。
下面小编画了一张添加水印的简易流程图:
绘制图片
接下来,进入文章主题,既然是要给图片添加水印,那么我们先来把图片绘制到 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 (!file) return;
const filePath = window.URL.createObjectURL(file); // 创建文件的临时路径: blob:http://localhost:8081/0cd115e2-9d4a-4c67-a86b-77e84d6f61db
const 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); // 绘制图片
}
},
};
</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.length
const u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new File([u8arr], name, {
type: mime
})
}
};
至此,本篇文章就写完啦,撒花撒花。
希望本文对你有所帮助,如有任何疑问,期待你的留言哦。
老样子,点赞+评论=你会了,收藏=你精通了。
暂无评论内容