使用canvas绘制平铺水印
大约 8 分钟...
提示
使用 canvas 绘制平铺水印, 适用于浏览器和 nodejs 环境
一、初版实现, 整体倾斜
本次绘制 text 块数量: 0
查看源码
<template>
<canvas id="myCanvas" :width="width" :height="height"></canvas>
</template>
<script setup>
import { ref, onMounted } from "vue";
const width = 780;
const height = 600;
const render = (canvas, params = {}) => {
const text = params.text || "水印Watermark";
const color1 = params.color1 || "rgba(0, 0, 0, 0.1)";
const color2 = params.color2 || "rgba(200, 0, 200, 0.2)";
const fontSize = 16; // 字体大小
const angle = 30; // 倾斜角度
const gapX = 50; // x方向间隔
const gapY = 60; // y方向间隔
const ctx = canvas.getContext("2d");
// 设置文字样式
ctx.font = `800 ${fontSize}px Arial`;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
// 文本宽高测量
const res = ctx.measureText(text);
const txtW = res.actualBoundingBoxLeft + res.actualBoundingBoxRight;
const txtH =
res.emHeightDescent ||
res.fontBoundingBoxDescent - res.fontBoundingBoxAscent;
// 平铺绘制水印
const cw = canvas.width;
const ch = canvas.height;
let _angle = (Math.PI / 180) * angle;
let xIndex = 0;
let yIndex = 0;
// -0.45什么都不是,只是个经验值
for (let x = cw * -0.45; x < cw + gapX; x += txtW + gapX, xIndex++) {
for (let y = -20; y < ch * 1.5 + gapY; y += txtH + gapY, yIndex++) {
ctx.rotate(_angle * -1);
ctx.fillStyle = (xIndex + yIndex) % 2 === 0 ? color1 : color2;
ctx.fillText(text, x, y);
ctx.rotate(_angle);
}
}
// 导出水印内容
/* let dataURL = canvas.toDataURL("image/png");
return dataURL; */
};
onMounted(() => {
const canvas = document.getElementById("myCanvas");
render1(canvas, {
text: "水印Watermark",
color1: "rgba(0, 0, 0, 0.1)",
color2: "rgba(200, 0, 200, 0.2)",
});
});
</script>
该实现可以自定义字体大小、文字颜色、x 和 y 方向间隔, 文字颜色交替显示
实际绘制时起始和结束的坐标均在 canvas 外, 保证水印能尽量完整覆盖 canvas 区域
二、改进实现, 竖直方向对齐
本次绘制 text 块数量: 0
查看源码
<template>
<canvas id="myCanvas" :width="width" :height="height"></canvas>
</template>
<script setup>
import { ref, onMounted } from "vue";
const width = 780;
const height = 600;
const render = (canvas, params = {}) => {
const text = params.text || "水印Watermark";
const color1 = params.color1 || "rgba(185, 185, 185, 0.35)";
const color2 = params.color2 || "rgba(185, 185, 185, 0.35)";
const fontSize = 16; // 字体大小
const angle = 30; // 倾斜角度
const gapX = 50; // x方向间隔
// const gapY = 60; // y方向间隔, 这里改到下面计算得到
const ctx = canvas.getContext("2d");
// 设置文字样式
ctx.font = `800 ${fontSize}px Arial`;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
// 文本宽高测量
const res = ctx.measureText(text);
const txtW = res.actualBoundingBoxLeft + res.actualBoundingBoxRight;
const txtH =
res.emHeightDescent ||
res.fontBoundingBoxDescent - res.fontBoundingBoxAscent;
// 平铺绘制水印
const cw = canvas.width;
const ch = canvas.height;
let _angle = (Math.PI / 180) * angle;
// 计算gapY, 这里为了保证在倾斜方向上对齐
const gapY = Math.sin(_angle) * txtW + Math.tan(_angle) * gapX;
let xIndex = 0;
let yIndex = 0;
for (let x = -6; x < cw + gapX; x += txtW + gapX, xIndex++) {
for (let y = -50; y < ch + gapY; y += txtH + gapY, yIndex++) {
// 保存当前画布状态
ctx.save();
// 平移到区块中心点
ctx.translate(x + txtW / 2, y + txtH / 2);
ctx.rotate(_angle * -1);
ctx.fillStyle = yIndex % 2 === 0 ? color1 : color2;
ctx.fillText(text, 0, 0);
// 恢复画布状态
ctx.restore();
}
}
// 导出水印内容
/* let dataURL = canvas.toDataURL("image/png");
return dataURL; */
};
onMounted(() => {
const canvas = document.getElementById("myCanvas");
render(canvas2, {
text: "水印Watermark",
color1: "rgba(0, 0, 0, 0.1)",
color2: "rgba(200, 0, 200, 0.2)",
});
});
</script>
相比于上一个实现, 该实现每一列是对齐的, 在倾斜角度方向上也是对齐的
性能上相较于上一个实现绘制次数明显更优:
- 方案 1: 绘制文本块数量: 0
- 方案 2: 绘制文本块数量: 0
倾斜角度的对齐是通过三角函数的计算得到 y 方向的间隔实现的, 具体计算过程如下:
如图所示:
绿色的 c
表示绘制的每一个 text 块, A
为倾斜角度, b1
为 x 方向间隔, 则竖直方向间隔 y = a + a1
;
三角形 abc
和 a1 b1
是等比的, 其中角度A
、长度c
(单个文本块宽度)、长度 b1
均已知, 则根据三角函数公式可以得到:a = sinA * c
;a1 = tanA * b1
;
所以 y = a + a1 = sinA * c + tanA * b1
;
对应于代码中的
const gapY = Math.sin(_angle) * txtW + Math.tan(_angle) * gapX;
三、nodejs 适配
获取 canvas 的方式不同,其他代码完全相同
const width = 780;
const height = 600;
const { createCanvas } = require("canvas");
const canvas = createCanvas(width, height);
const ctx = canvas.getContext("2d");
// ... 其他代码完全相同