跳至主要內容

使用canvas绘制平铺水印

安格前端前端html5大约 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 方向的间隔实现的, 具体计算过程如下:

cyaa1ccAAbb1

如图所示:
绿色的 c 表示绘制的每一个 text 块, A 为倾斜角度, b1 为 x 方向间隔, 则竖直方向间隔 y = a + a1;
三角形 abca1 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");
// ... 其他代码完全相同


 
 


附: 直角三角形三角函数定义

sine
直角三角形三角函数
上次编辑于:
你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.1.3