深色模式
数字水印
本文介绍常用的数字水印方案
参考:
LSB图片算法
利用图片中 RGB 通道中 R通道对图片像素影响最小,更改数据,将内容隐藏在其中。
预览链接 网页预览打开
优缺点
- 优点: 不需要原图就能恢复水印
- 缺点:可以被攻击破解
Canvas 获取图片数据大小为 图片 width * height * 4
, 前 3位是 RGB
3 个通道的数据,第 4 位是透明值,前端常见的 RGBA
。
加密规则:对 R 通道最低位进行写入数据,如果要写入数据在该位有数据,则最低位设为 1,否则设为0, 反映到 RGB 数值上,则是 最低位为1 時为 技术,否则为 偶数。
解密规则: 对 R 通道进行处理,R 的分量最低位为 1 则该像素设为红色,R 的分量最低位为 0 则该像素设为黑色
代码
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>canvas-dark-watermark</title>
</head>
<body>
<canvas id="canvas" width="300" height="400"></canvas>
<canvas id="canvas2" width="300" height="400"></canvas>
<script>
function mergeData(ctx, newData, color, originalData) {
const oData = originalData.data;
let bit;
let offset;
switch (color) {
case 'R':
bit = 0;
offset = 3;
break;
case 'G':
bit = 1;
offset = 2;
break;
case 'B':
bit = 2;
offset = 1;
break;
}
const dataLength = oData.length;
// 只处理目标通道数据
for (let i = bit; i < dataLength; i += 4) {
// 新数据最低位为0
if (newData[i + offset] === 0 && oData[i] % 2 == 1) {
if (oData[i] === 255) {
oData[i]--;
} else {
oData[i]++;
}
} else if (newData[i + offset] !== 0 && oData[i] % 2 === 0) {
// 新数据存在
if (oData[i] === 255) {
oData[i]--;
} else {
oData[i]++;
}
}
}
ctx.putImageData(originalData, 0, 0);
}
function processData(ctx, originalData) {
var data = originalData.data;
for (let i = 0; i < data.length; i++) {
if (i % 4 == 0) {
// R分量
if (data[i] % 2 == 0) {
data[i] = 0;
} else {
data[i] = 255;
}
} else if (i % 4 == 3) {
// alpha通道不做处理
continue;
} else {
// 关闭其他分量,不关闭也不影响答案
data[i] = 0;
}
}
// 将结果绘制到画布
ctx.putImageData(originalData, 0, 0);
}
function encodeImg(src) {
const canvas = document.getElementById('canvas');
let textData;
let originalData;
const canvasWidth = canvas.width;
const canvasHeight = canvas.height;
const ctx = canvas.getContext('2d');
ctx.font = '40px Microsoft Yahei';
ctx.fillStyle = 'red';
ctx.fillText('得時笔记', 50, 100, canvasWidth, canvasHeight);
textData = ctx.getImageData(0, 0, canvasWidth, canvasHeight).data;
const img = new Image(canvasWidth, canvasHeight);
img.src = src;
img.onload = function () {
ctx.drawImage(img, 0, 0, canvasWidth, canvasHeight);
originalData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
mergeData(ctx, textData, 'R', originalData);
};
}
function decodeImg() {
const ctx = document.getElementById('canvas').getContext('2d');
const ctx2 = document.getElementById('canvas2').getContext('2d');
const originalData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
console.log(originalData);
processData(ctx2, originalData);
}
encodeImg('/img/flower.jpg');
setTimeout(() => {
decodeImg();
}, 1000);
</script>
<style>
#canvas {
border: 1px solid red;
float: left;
}
#canvas2 {
border: 1px solid green;
float: left;
}
#canvas3 {
border: 1px solid green;
float: left;
}
</style>
</body>
</html>
:::