canvas 生成水印图片


theme: smartblue
highlight: atom-one-dark

canvas-water-mark.png

不到50行代码实现一个canvas生成水印工具函数,使用时也非常简单,直接调用即可;先贴完整代码,再详解思路:

源码地址

/**
 * 给图片添加水印,并返回`base64`
 * @param {object} params
 * @param {string} params.img 图片地址,支持CDN和base64
 * @param {string} params.title 水印标题
 * @param {string=} params.desc 水印描述
 * @param {"jpeg"|"png"=} params.type 生成图片的类型,默认`"jpeg"`
 * @param {string=} params.color 水印颜色,默认`rgba(255, 255, 255, 0.5)`
 * @param {number=} params.markSize 水印矩阵宽高(默认150)
 * @param {number=} params.fontSize 水印字体大小(默认12)
 * @param {number=} params.angle 水印旋转的角度(默认315则-45)
 * @returns {Promise<string>}
 */
function getWaterMarkImage(params) {
  const canvas = document.createElement("canvas");
  const context = canvas.getContext("2d");
  canvas.style.cssText = "position: fixed; top: -110%; left: -110%; z-index: -10;";
  document.body.appendChild(canvas);
  const image = new Image();
  image.crossOrigin = "Anonymous";
  image.src = params.img;
  function getDrawMark() {
    const mark = document.createElement("canvas");
    const markSize = params.markSize || 150;
    const radius = markSize / 2;
    mark.width = markSize;
    mark.height = markSize;
    const ctx = mark.getContext("2d");
    const title = params.title || "";
    const desc = params.desc || "";
    const fontSize = params.fontSize || 12;
    const angle = params.angle || 315;
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    ctx.font = `${fontSize}px Microsoft Yahei`;
    ctx.fillStyle = params.color || "rgba(255, 255, 255, 0.5)";
    // TODO: 必须要设置完字体样样式大小再获取字体高度
    // const titleWidth = ctx.measureText(title).width;
    // const descWidth = ctx.measureText(desc).width;
    ctx.translate(radius, radius);
    ctx.rotate(angle * Math.PI / 180);
    ctx.translate(-radius, -radius);
    if (desc) {
      ctx.fillText(title, radius, radius - fontSize);
      ctx.fillText(desc, radius, radius + fontSize * 0.6);
    } else {
      ctx.fillText(title, radius, radius);
    }
    return mark;
  }
  return new Promise(function (resolve) {
    image.onload = function () {
      canvas.width = image.width;
      canvas.height = image.height;
      context.drawImage(image, 0, 0);
      context.fillStyle = context.createPattern(getDrawMark(), "repeat");
      context.fillRect(0, 0, canvas.width, canvas.height);
      const base64 = canvas.toDataURL(`image/${params.type || "jpeg"}`, 1);
      resolve(base64);
      canvas.remove();
    }
    image.onerror = function () {
      resolve("");
      canvas.remove();
    }
  });
}

步骤1-处理传入的图片

生成水印原理都知道,就是在图片上层贴一层自定义的标识(牛皮癣),至于怎么个贴法就百花齐放了;所以这里先要加载传入的图片,然后用canvas绘制出来,像下面这样:

方法需要配置的参数参考说明上面,这里不重复了

function getWaterMarkImage(params) {
  const canvas = document.createElement("canvas");
  const context = canvas.getContext("2d");
  canvas.style.cssText = "position: fixed; top: -110%; left: -110%; z-index: -10;";
  document.body.appendChild(canvas);
  const image = new Image();
  image.crossOrigin = "Anonymous";
  image.src = params.img;
  function getDrawMark() {
    ...// 下面分解说明
  }
  return new Promise(function (resolve) {
    image.onload = function () {
      canvas.width = image.width;
      canvas.height = image.height;
      context.drawImage(image, 0, 0);
      context.fillStyle = context.createPattern(getDrawMark(), "repeat");
      context.fillRect(0, 0, canvas.width, canvas.height);
      const base64 = canvas.toDataURL(`image/${params.type || "jpeg"}`, 1);
      resolve(base64);
      canvas.remove();
    }
    image.onerror = function () {
      resolve("");
      canvas.remove();
    }
  });
}

生成图片并绘制的时候,注意要给图片对象添加crossOrigin = "Anonymous",不然非同域的图片会生成失败!接着看context.fillStyle = context.createPattern(getDrawMark(), "repeat");这行代码,与css中的背景图功能类似,不同的在canvas中,它是贴在上面的;所以省去了计算水印并铺满图片的操作,而第一个参数则可以为canvas对象或者图片对象传入生成多个并重复铺满,所以水印的生成只需要绘制一个canvas就可以了。

步骤2-绘制自定义水印文字

因为水印平铺已经一行代码搞定,所以接下来只需要把单个水印绘制出来即可;绘制之前,先单独写一个方法并绘制到浏览器中,方便调试和查看效果,像这样:

function rotateTest() {
  const canvas = document.createElement("canvas");
  const ctx = canvas.getContext("2d");
  const size = 200;
  const radius = size / 2;
  canvas.width = size;
  canvas.height = size;
  canvas.style.cssText = "border: 1px solid orange";
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  ctx.font = `20px Microsoft Yahei`;

  function run() {
    ctx.fillText("content", radius, radius);
  }

  document.body.appendChild(canvas);
  run();
}

rotateTest();

效果图:

canvas-1.png

使用fillText可以绘制文字,让文字居中显示则使用盒子的半径去作为xy坐标即可,由于水印是带有旋转角度的,接着再加个旋转效果看看:

// 省略上面代码
function run() {
  ctx.rotate(30 * Math.PI / 180); // 先旋转,再设置文字,不然不生效
  ctx.fillText("content", radius, radius);
}

效果图:

canvas-2.png

可以看到不是想要的效果,和我理解的rotate不一样,后面去查了一下,原来canvas的旋转是以左上角为中心点进行旋转的,和css中的transform: rotate()不一样。为了方便调试,我把字体先去掉,换成矩阵盒子来做个动态的角度变化,像这样:

let angle = 0;

function run() {
  ctx.clearRect(0, 0, size, size);//清屏
  ctx.save();
  ctx.beginPath();
  ctx.fillStyle = "orange";
  ctx.rotate(angle * Math.PI / 180)//设置矩形旋转
  ctx.fillRect(0, 0, radius, radius);//fillrect(矩形宽度的一半,矩形高度的一半,矩形长度,矩形宽度)
  ctx.restore();
  angle += 10;
  requestAnimationFrame(run);
}

效果图:

step1.gif

有了视觉效果,那么实现基于中心点旋转就好调试了;既然没有相关api设置旋转中心,那么就换一个思路,先将矩阵移动到cavans矩阵的中心点,然后旋转到指定的角度,再设置负数的原来偏移值,最后再绘制文字,这样逻辑上就达到了基于矩阵中心旋转的效果了,改造一下代码之后:

function run() {
  ctx.clearRect(0, 0, size, size);//清屏
  ctx.save();
  ctx.beginPath();
  ctx.fillStyle = "orange";
  ctx.translate(radius, radius);//围绕矩形的中心点旋转
  ctx.rotate(angle * Math.PI / 180)//设置矩形旋转
  ctx.translate(-radius, -radius);
  ctx.fillText("content", radius, radius);
  ctx.strokeRect(0, 0, size, size);
  ctx.restore();
  angle++;
  requestAnimationFrame(run);
}

效果图:

step2.gif

这样,就可以从外面传入旋转的角度,去自定水印的旋转位置,同时大小也可以根据图片实际大小去设置水印的尺寸和间距,是我想要的,比较完美的最终效果。

最后一步,把思路代码改写到getDrawMark函数中就大功告成了:

function getDrawMark() {
  const mark = document.createElement("canvas");
  const markSize = params.markSize || 150;
  const radius = markSize / 2;
  mark.width = markSize;
  mark.height = markSize;
  const ctx = mark.getContext("2d");
  const title = params.title || "";
  const desc = params.desc || "";
  const fontSize = params.fontSize || 12;
  const angle = params.angle || 315;
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  ctx.font = `${fontSize}px Microsoft Yahei`;
  ctx.fillStyle = params.color || "rgba(255, 255, 255, 0.5)";
  // TODO: 必须要设置完字体样样式大小再获取字体高度
  // const titleWidth = ctx.measureText(title).width;
  // const descWidth = ctx.measureText(desc).width;
  ctx.translate(radius, radius);
  ctx.rotate(angle * Math.PI / 180);
  ctx.translate(-radius, -radius);
  if (desc) {
    ctx.fillText(title, radius, radius - fontSize);
    ctx.fillText(desc, radius, radius + fontSize * 0.6);
  } else {
    ctx.fillText(title, radius, radius);
  }
  return mark;
}
© 版权声明
THE END
喜欢就支持一下吧
点赞5 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容