一、写在前面
水印分为明水印和暗水印,明水印即肉眼可见,暗水印则是将水印透明度降低到肉眼不可识别范围,如果有暗水印当有用户将图片或截图转发到其他网络平台,这时对图片进行一些操作可以水印显示出来,从而追踪到泄露消息的人,起到保护信息安全作用。ImageMagick/graphicsmagick/gm已多年不维护。 本文主要梳理数字水印的相关介绍与实现方式,并添附代码片段可开箱即用,业务中根据实际需求场景参考。
二、关于数字水印
包括明暗水印、融合水印、浮雕水印等。是一种信息保护技术,利用人体感官的限制将数字信号,如图像、文字、符号、数字等可以作为标记、标识的信息与原始数据(如图像、文档、音频、视频)紧密结合并隐藏或显示其中,且可以经过一些不破坏源数据的操作保存下来,从而保护信息。
- 如果业务需求是给图片添加通用水印,可在node环境中基于Images生成。
- 如果业务安全要求较高的图像素材则考虑通过频域添加,是指通过某种变换手段(傅里叶变换,离散余弦变换,离散小波变换等)将图像变换到频域,在频域对图像添加水印即从编码根源处理且不影响视觉,再通过逆变换解析其中信息。频域手段隐匿性更强,抗攻击性更高。
- 鲁棒性,即健壮性,指水印在被处理过后仍能被识别到,如图像压缩、滤波、尺寸发生变化等。
- 防止被去除,水印最终都以dom元素呈现,要防止浏览器控制台被恶意修改去除。
三、实现方案
方案一 JS实现
通过canvas(或svg)+水印文案生成base64的编码图片data,结合Mutation API以背景图显示到需要水印范围的dom容器中,此处以canvas为例。
优点:实现便捷,不易被去除。
缺点:将资源文件暴露在前端,安全性较低。
实现如下:首先封装watermark方法,在react中执行使用。
// watermark.js
const watermark = (props) => {
const {
container = document.body,
width = '300px',
height = '240px',
textAlign = 'center',
verticalAlign = 'middle',
font = "18px Microsoft Yahei",
fillStyle = 'rgba(100, 100, 100, 0.2)',//水印颜色
content = '机密内容',
rotate = -20,
} = props;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext("2d");
canvas.setAttribute('width', width);
canvas.setAttribute('height', height);
ctx.textAlign = textAlign;
ctx.verticalAlign = verticalAlign;
ctx.font = font;
ctx.fillStyle = fillStyle;
ctx.rotate(Math.PI / 180 * rotate);
ctx.fillText(content, parseFloat(width) / 2, parseFloat(height) / 2);
const base64Url = canvas.toDataURL('image/png', 0.92);
const _wm = document.querySelector('._wm');
const watermarkDiv = _wm || document.createElement("div");
const styleStr = `
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background: url('${base64Url}');
pointer-events: none;`;
watermarkDiv.setAttribute('style', styleStr);
watermarkDiv.classList.add('_wm');
if (!_wm) {
container.style.position = 'relative';
container.insertBefore(watermarkDiv, container.firstChild);
}
const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
if (MutationObserver) {
let observer = new MutationObserver(() => {
const _wm = document.querySelector('._wm');
if ((_wm && _wm.getAttribute('style') !== styleStr) || !_wm) {
observer.disconnect();
observer = null;
watermark(props);
}
});
observer.observe(container, {
attributes: true,
childList: true,
characterData: true,
subtree: true
});
}
}
export default watermark;
复制代码
在react 中使用watermark,
import React, { useEffect } from "react";
import watermark from './water.js';
const Comp = () => {
useEffect(() => {
watermark({
content: '水印测试机密内容', // 水印文本
container: document.getElementById('water') // 水印容器区域
});
}, []);
return (
<div id="water" style={{width: 800, height: 400}}>
<div>balabala...</div>
<div>balabala...</div>
<div>balabala...</div>
</div>
);
};
复制代码
效果如下:
注:可用canvas或svg格式,参考实际场景与浏览器兼容性。 MutationObserver接口提供了监视对DOM树所做更改的能力。它被设计为旧的Mutation Events功能的替代品,该功能是DOM3 Events规范的一部分。浏览器兼容性如下。
方案二 Node实现
Node 端基于Sharp根据登录用户信息经鉴权认证后生成图片返回到前端。 后端动态生成原始数据提高安全性,但比较纯前端生成性能略显低。 推荐sharp + text-to-svg工具配合,不推荐gm、imagemagick已多年不维护。 实现如下,首先封装添加水印方法,相关API通过Sharp配置。
const sharp = require('sharp');
const TextToSVG = require('text-to-svg');
const textToSVG = TextToSVG.loadSync();
// 添加水印
const addText = async(bgImg, font = {}, filePath) => {
const {
fontSize = 14,
text = '机密内容',
color = 'rgba(240,241,243, 0.4)',
left = 0,
top = 0
} = font;
// 设置文字属性
const attributes = {
fill: color
};
const options = {
fontSize,
anchor: 'top',
attributes
};
// 文字转svg 再buffer
const svgTextBuffer = Buffer.from(textToSVG.getSVG(text, options));
// 写入文字水印
await sharp(bgImg || {
create: {
width: 200,
height: 200,
channels: 4,
background: { r: 255, g: 255, b: 255, alpha: 0 }
}
})
.rotate(0)
.resize(200, 200)
.composite([
{
input: svgTextBuffer,
top,
left,
}
])
.png()
.toFile(filePath)
.catch(err => {
console.log(err)
});
};
复制代码
node调用并返回到前端页面,此处在controller中演示。
class PageController extends Controller {
async test() {
const { ctx } = this;
await addText(
path.join(__dirname, 'bg.jpg'), // 背景图片,如果null则生成透明背景加文字水印
{
// fontSize: 20,
text: '机密内容',
left: 100,
top: 160
},
path.join(__dirname, 'out.png') // 图片生成路径
);
ctx.type = 'image/png';
ctx.status = 200;
const curPath = path.join(__dirname, 'out.png');
const file = fs.readFileSync(curPath);
ctx.body = file;
}
}
module.exports = PageController;
复制代码
效果图片如下
方案三 暗水印加密优化
为更高提升安全性,可在nodejs中生成明水印和暗水印结合(或可通过集团CRO产品的接入文档参阅接入相关产品),一层明水印(用户视觉可见)显示用户名称信息,一层暗水印(用户视觉不可见方式)显示通过md5等加密后的用户信息,前端结合Mutationovserver API防去除。
代码实现方式即方案一方案二的结合优化:
- nodejs中 sharp + text-to-svg + md5生成图片后返回到网页端,代码同方案二。
- 网页端请求获取图片结合window.MutationObserver的API渲染。
...
axios.get('/watermark') // 获取图片watermark.png
...
let MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
let dom = document.getElementById('root'); // 添加水印dom
let observer = new MutationObserver(mutations => {
dom.style.background = "url('watermark.png')";
});
observer.observe(dom, {
attributes: true,
childList: true,
characterData: true,
subtree: true
});
复制代码
四、总结
增加水印可有效防止信息泄露与知识产权侵犯,其实现中也需业务结合考虑性能资源和安全要求程度等方面。如截图或者去掉水印截图,均可通过PS等图片制作工具将图层颜色覆盖调试显示出水印追踪到个人,但如果图片损坏严重也会对信息追踪增大难度甚至获取不到,所以也需要在日常中提高信息安全的重视。
参考文章:
ImageMagick图像处理 www.imagemagick.com.cn/
Web API接口 developer.mozilla.org/zh-CN/docs/…
Sharp 图像合成 sharp.pixelplumbing.com/api-composi…