显性水印和不可见数字水印
显性水印(代码见最后)
-
获取要绘制的画布所在元素
-
获取浏览器的dpr(devicePixelRatio),将画布的宽度和高度乘以dpr, 否则绘制出来的画布会变得模糊
-
创建Image元素开始绘制原始图片
-
绘制显性水印, 设置水印的样式, 水印位置设置在图片右下角
不可见数字水印
function getBitOffset(color):
获取RGB中某一分量对应的位和偏移量
图片编码
function encodeImg(src):
-
获取画布元素的Context
-
画布宽高乘以dpr(devicePixelRatio)
-
绘制水印, 使用getImageData()方法获得水印的像素信息
-
绘制图片, 使用getImageData()方法获得图片的像素信息
-
调用mergeData()方法合并像素信息
function mergeData(ctx, newData, color, originalData):
1. 合并原始图片数据和数字水印的rgb数据.
2. 对于规定的某一个RGB分量,
采用将把没有水印信息的像素通过自增方式全改成偶数,
把有水印信息的像素自增全改成奇数这种编码的方式编码图片
- 绘制合并后的图片
图片解码
function decodeImg(src,color):
- 同encodeImg()方法一样, 使用getImageData()方法获得图片的RGB信息
- 调用processData()方法
function processData(ctx, originalData,color):
- 解码的方式与编码的方式相反
- 将非指定的RGB分量的所有信息置0(像素点置黑)
- 对于指定的RGB分量, 将偶数部分(非水印信息)的信息置0, 其他信息置为255
- 对于alpha通道的信息不处理
结果如图
代码
未解决跨域问题, 不可从外部直接打开html, 需从IDE打开
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>watermark</title>
</head>
<style>
html{
margin:0;
padding: 0;
}
.app{
display: flex;
justify-content: space-around;
flex-wrap: wrap;
}
.canvas{
height: 200px;
width: 540px;
margin-top: 50px;
}
img{
height: 200px;
width: 540px;
margin-top: 50px;
}
</style>
<body>
<div class="app">
<img src="../images/1.jpg">
<canvas class="canvas" id="canvas-mark"></canvas>
<canvas class="canvas" id="encode-mark"></canvas>
<canvas class="canvas" id="encode-image"></canvas>
</div>
<script>
//绘制显性水印
var canvas_mark=document.getElementById("canvas-mark")
var dpr = (scale = window.devicePixelRatio || 1);
var rect = canvas_mark.getBoundingClientRect();
canvas_mark.width = rect.width * dpr;
canvas_mark.height = rect.height * dpr;
canvas_mark.style.width = rect.width + "px";
canvas_mark.style.height = rect.height + "px";
let ctx=canvas_mark.getContext('2d');
let img = new Image();
img.onload = function () {
ctx.drawImage(img, 0, 0, canvas_mark.width, canvas_mark.height);
const txt = '@ ChenYin';
ctx.fillStyle = '#fff';
ctx.globalAlpha = 1;
ctx.font = `12px 微软雅黑 light`;
ctx.textAlign = 'right';
ctx.fillText(txt, canvas_mark.width - 10, canvas_mark.height - 10);
}
img.src="../images/1.jpg"
//绘制数字水印和数字水印结果图
//获取RGB中某一分量对应的位和偏移量
function getBitOffset(color) {
let bit, offset;
switch (color) {
case 'R':
bit = 0;
offset = 3;
break;
case 'G':
bit = 1;
offset = 2;
break;
case 'B':
bit = 2;
offset = 1;
break;
}
return [bit,offset];
}
//图片编码
//合并原始图片数据和数字水印的rgb数据, 采用将把没有信息的像素全改成偶数, 把有信息的像素全改成奇数这种编码的方式
function mergeData(ctx, newData, color, originalData) {
let oData = originalData.data;
let [bit,offset]=getBitOffset(color);
for (var i = 0; i < oData.length; i++) {
if (i % 4 == bit) {
// 只处理目标通道
//把没有信息的像素全改成偶数
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)) {
// // 有信息的像素,该通道最低位置1,可以想想上面的斑点效果是怎么实现的
oData[i]++;
}
}
}
ctx.putImageData(originalData, 0, 0);
}
function encodeImg(src) {
var textData;
var can=document.getElementById('encode-mark');
var dpr = (scale = window.devicePixelRatio || 1);
var rect = canvas_mark.getBoundingClientRect();
can.width = rect.width * dpr;
can.height = rect.height * dpr;
can.style.width = rect.width + "px";
can.style.height = rect.height + "px";
var ctx = can.getContext('2d');
ctx.font = '30px Microsoft Yahei';
ctx.fillText('ChenYin', 200, 120);
textData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height).data;
var img = new Image();
// img.crossOrigin = '';
var originalData;
img.onload = function () {
// 获取指定区域的canvas像素信息
ctx.drawImage(img, 0, 0,can.width,can.height);
originalData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
mergeData(ctx, textData, 'G', originalData)
};
img.src = src;
}
//图片解码
function processData(ctx, originalData,color) {
let data = originalData.data;
let [bit,offset]=getBitOffset(color);
for (var i = 0; i < data.length; i++) {
if (i % 4 == bit) {
if (data[i] % 2 == 0) {
data[i] = 0;
} else {
data[i] = 255;
}
} else if (i % 4 == 3) {
continue;//alpha通道不处理
} else {
data[i] = 0;
}
}
// 将结果绘制到画布
ctx.putImageData(originalData, 0, 0);
}
function decodeImg(src,color) {
var encode_image = document.getElementById('encode-image')
var dpr = (scale = window.devicePixelRatio || 1);
var rect = canvas_mark.getBoundingClientRect();
encode_image.width = rect.width * dpr;
encode_image.height = rect.height * dpr;
encode_image.style.width = rect.width + "px";
encode_image.style.height = rect.height + "px";
var ctx=encode_image.getContext('2d');
var img = new Image();
var originalData;
img.onload = function () {
// 获取指定区域的canvas像素信息
ctx.drawImage(img, 0, 0);
originalData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
console.log(originalData)
processData(ctx, originalData,color)
};
img.src = src;
}
encodeImg('../images/1.jpg');
decodeImg('../images/1-encode.png','G');
</script>
</body>
</html>