为图片披上个性水印之袍

654 阅读3分钟

前言

经常会看到别人的博客中的图片资源上都会带有自己的标识的水印,以此来强调图片的来源、作者等信息。
就以掘金来说,当你进行创作的时候,在文章中上传图片之后就会看见图片上默认加了水印-- 稀土掘金社区 @ 作者名称

image (1).png

这是可以体验图片加水印的网址,具体实现原理可以看下面

实践

实现水印的方式有很多种,例如通过css将文字定位在图片上方,然后通过 html2Canvas 插件将html元素转换成图片;还有一种就是通过 canvas 来实现,就是将上传的图片设置为画布的背景然后再渲染文字。

本文就讲解通过 canvas 来实现水印功能。

首先我们需要一个上传的组件,然后将上传的图片渲染到画布之上。

// html
<input type="file" @change="handleFileUpload" accept="image/*"/>
<canvas ref="canvas"></canvas>

// js
const canvas = ref(null)

// 上传change事件
const handleFileUpload = (e) => {
  const file = e.target.files[0];
  const reader = new FileReader();
  reader.onload = (readerEvent) => {
    handleDraw(readerEvent.target.result)
  }
  reader.readAsDataURL(file)
}

// 画布绘制图片
const handleDraw = (url) => {
  const img = new Image();
  img.onload = () => {
    canvas.value.width = img.width;
    canvas.value.height =img.height;
    const ctx = canvas.value.getContext('2d')
    ctx.drawImage(img, 0, 0, img.width, img.height);
  }
  img.src = url
}

效果如下

recording.gif

接下来我们只需要将所需的水印文字绘制到画布之上

画布绘制文字就是通过 ctx.fillText(text,x,y) 来实现。

ctx.font = '20px Arial';
ctx.fillStyle = '#ffffff';
ctx.fillText('稀土掘金社区 @ 前端笨鸟', 0, 0)

设置文本字体样式然后绘制发现文本就出现了一点点尾部,并没有完整的绘制在图片的左上角。
然后去查了下canvas的文本绘制的文本基线 textBaseline 属性

context.textBaseline="alphabetic|top|hanging|middle|ideographic|bottom";

textBaseline.png

默认值为 alphabetic,可以看出来默认的文本基线属性绘制的文字基线以上,我们可以改成 Bottom ,因为 TopAlphabetic 都会遮挡一点文字,再加上文本高度即可。

ctx.textBaseline="bottom"; 
ctx.font = '20px Arial';
ctx.fillStyle = '#ffffff';
ctx.fillText('稀土掘金社区 @ 前端笨鸟', 0, 20)

mask.png

现在我们只需要移动到右下角,修改一下坐标值

const text = '稀土掘金社区 @ 前端笨鸟'
const textWidth = ctx.measureText(text).width;
const padding = 5
ctx.fillText(text,img.width - textWidth - padding, img.height - padding)

mask_2.png

大功告成 最基本的水印功能就完成了。

还有一种类似全图布满水印样式,如下

mask_3.png

这种的绘制原理也差不多,主要就是需要通过 rotate 来旋转画布,然后通过循环来遍历文字,重点在于怎样计算遍历的文本的一个区域的宽高。

我们先看看正常的样子(即以画布的宽高作为文本的绘制区域)旋转之后的样子

const interval = 50
const angleInRadians = Math.PI / 6;
ctx.rotate(-1 * angleInRadians)
for (let x = 0; x < width; x += interval + textWidth ) {
  for (let y = 0; y < height; y += +interval ) {
    ctx.fillText(text, x, y);
  }
}

mask_4.png

可以发现左下和右下的三角区域没有绘制到,所以我们需要重新来计算一下绘制的区域

mask_5.png

需要计算 XY的距离,这个时候就需要拿起读书的时候的 三角函数

正弦(sin):对边比斜边

所以 X = sinα * height
同理也可得 Y = sinα * width

const angleInRadians = Math.PI / 6;
const sinValue = Math.sin(angleInRadians);
const rotatedX = sinValue * img.height
const rotatedY = sinValue * img.width
ctx.rotate(-1 * angleInRadians)
for (let x = -rotatedX; x < img.width +rotatedX; x += interval + textWidth ) {
  for (let y = -rotatedY; y < img.height + rotatedY; y += +interval ) {
    ctx.fillText(text, x, y);
  }
}

mask_6.png

这样就大功告成了

导出

导出图片的话就比较简单,使用了 blueimp-canvas-to-blob 可以对图片进行压缩。

// 默认导出
const link = document.createElement('a');
link.href = canvas.value.toDataURL('image/png');
link.download = 'mark.png';
link.click();

// 压缩导出
canvas.value?.toBlob((blob) => {
    // 处理生成的 Blob 对象
    if (blob) {
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'mask.jpg';
    a.click();
    }
}, 'image/jpeg', 0.8); // 设置图片格式和压缩质量

是不是很简单,具体体验可移步这里,欢迎大家一起讨论,指出问题,感兴趣的 jym 可以给项目star支持一下,谢谢大家