前端水印生成方案

前端水印生成方案

image-20210203173951950.png
image-20210203174202734.png

举个🌰:

视觉中国网站的图片有明显的水印:

image-20210203174458167.png

知乎图片:

image-20210203174630128.png
{15AAFDB9-59F0-458E-93EF-AE4E799FA5D2}

以上是图片上加水印:

背景水印🌰:

钉钉软件背景水印

image-20210203175044062.png

思路:

生成网页水印:

  1. 利用canvas生成图片,动态生成div填充整个背景,将生成的图片用于background-image属性上,进行页面填充,然后通过Mutation Observer监视节点div的变动防止水印被删除。

  2. 通过svg实现,实现原理与canvas相同, 相比Canvas,SVG有更好的浏览器兼容性,使用SVG生成水印的方式与Canvas的方式类似,只是base64Url的生成方式换成了SVG。

  3. 通过NodeJS生成水印。

生成图片水印:

  1. 通过canvas给图片加水印:读取图片,在图片加载完之后用canvas填充图片,并填充加密文字,然后将图片转成base64url传值给回调函数。

这样似乎能满足我们的需求了,但是还有一个问题,稍微懂一点浏览器的使用或者网页知识的用户,可以用浏览器的开发者工具来动态更改DOM的属性或者结构就可以去掉了。这个时候有两个解决办法:

1.监测水印div的变化,记录刚生成的div的innerHTML,每隔几秒就取一次新的值,一旦发生变化,则重新生成水印。但是这种方式可能影响性能;

2.使用MutationObserver

Mutation Observer API 用来监视 DOM 变动。DOM 的任何变动,比如节点的增减、属性的变动、文本内容的变动,这个 API 都可以得到通知。

使用MutationObserver构造函数,新建一个观察器实例,实例的有一个回调函数,该回调函数接受两个参数,第一个是变动数组,第二个是观察器实例。MutationObserver 的实例的observe方法用来启动监听,它接受两个参数。

第一个参数:所要观察的 DOM 节点,第二个参数:一个配置对象,指定所要观察的特定变动,有以下几种:

img

MutationObserver只能监测到诸如属性改变、增删子结点等,对于自己本身被删除,是没有办法的可以通过监测父结点来达到要求。

demo地址:https://www.mwcxs.top/static/testTool/index.html

基本的最低位隐藏文本信息:

{15AAFDB9-59F0-458E-93EF-AE4E799FA5D2}

我们知道图片的像素信息里存储着 RGB 的色值,R、G、B 分别为该像素的红、绿、蓝通道,每个通道的分量值范围在 0~255,16 进制则是 00~FF。在 CSS 中经常使用其 16 进制形式,比如指定博客头部背景色为 #A9D5F4。其中 R(红色)的 16 进制值为 A9,换算成十进制为 169。这时候,对 R 分量的值+1,即为 170,整个像素 RGB 值为 #AAD5F4,别说你看不出差别,就连火眼金金的“ 像素眼” 设计师都察觉不出来呢。于此同时,修改 G、B 的分量值,也是我们无法察觉的。因此可以得出重要结论:RGB 分量值的小量变动,是肉眼无法分辨的,不影响对图片的识别。

打印出数据,会看到有一个非常大的数组。

QQ截图20160325174947

这个一维数组存储了所有的像素信息,一共有 256 * 256 * 4 = 262144 个值。其中 4 个值一组,为什么呢?在浏览器中解析图片,除了 RGB 值外,每组第 4 个值为透明度值,即像素信息实际为大家熟知的 rgba 值。

这里的解密规则是对 R 通道进行处理,R 的分量最低位为 1 则该像素设为红色,R 的分量最低位为 0 则该像素设为黑色,直接看代码实现,完成后我们再绘制到 canvas,即可看到结果。

在图片中隐藏信息

讲了基础的解密过程,再来反向说说加密过程。

既然要在图片中加入文字信息,那么首先要获取文字的像素信息,这里我先用 canvas 在画布上打印文字,获取像素信息。

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="javascript" cid="n49" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">var textData;
// 这些canvas API,好久没用,需要查API文档了T_T
ctx.font = '30px Microsoft Yahei';
ctx.fillText('广告位招租u', 60, 130);
textData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height).data;</pre>

先保存文字的像素信息,接着加载图片获取其像素信息,然后对两组像素进行处理,我在这里抽离了一个公共方法。

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="javascript" cid="n51" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; background-position: inherit inherit; background-repeat: inherit inherit;">var mergeData = function(newData, color){
var oData = originalData.data;
var bit, offset; // offset的作用是找到alpha通道值,这里需要大家自己动动脑筋

switch(color){
case 'R':
bit = 0;
offset = 3;
break;
case 'G':
bit = 1;
offset = 2;
break;
case 'B':
bit = 2;
offset = 1;
break;
}

for(var i = 0; i < oData.length; i++){
if(i % 4 == bit){
// 只处理目标通道
if(newData[i + offset] === 0 && (oData[i] % 2 === 1)){
// 没有信息的像素,该通道最低位置0,但不要越界
if(oData[i] === 255){
oData[i]--;
} else {
oData[i]++;
}
} else if (newData[i + offset] !== 0 && (oData[i] % 2 === 0)){
// // 有信息的像素,该通道最低位置1,可以想想上面的斑点效果是怎么实现的
if(oData[i] === 255){
oData[i]--;
} else {
oData[i]++;
}
}
}
}
ctx.putImageData(originalData, 0, 0);
}</pre>

上述代码做的是,接受要隐藏的数据以及隐藏的颜色通道,然后对原图进行操作,修改图片该通道分量的最低位,如果有文字信息,则最低位置为 1,否则为 0。从最文章开头的结论知道,RGB 的三个通道可以分别隐藏不同信息。

在 img.onload 中调用 mergeData(textData, 'R'),处理好图像后,只要在浏览器中的 canvas 上右键保存图片即可。

这里的例子比较简单,只展示了基本的最低位隐藏文本信息,像二维码这些简单图形也可以这么处理。现实中隐藏画中画则需要更专业的图像处理算法,这里就不再展开了。

image-20210203173652369.png

http://localhost:3000/

二、复杂 频域制定数字盲水印

信号是有频率的,一个信号可以看做是无数个不同阶的正弦信号的的叠加。

数字水印:

主要作用就是标记版权和检测版权(辨认真伪)

[图片上传失败...(image-fa4c1f-1643205526696)]

https://blog.csdn.net/aria_miazzy/article/details/99649737

频域制定数字盲水印

频域方式图片合并水印是一个从图片的编码方式进行合成水印,水印不可见,直接反向编码才可以拿到水印,从最根本上解决了数据安全和数据泄露后的追踪问题。

信号是有频率的,一个信号可以看做是无数个不同阶的正弦信号的的叠加。

[图片上传失败...(image-528079-1643205526696)]

img

1、编码的目的有二,一是对水印加密,二控制水印能量的分布。以下是叠加数字盲水印的实验。

(1)原图像。尺寸300*240 ,汉子一枚,

img

(2)水印照片。

img

(3)水印编码。编码方式采用随机序列编码,通过编码,水印分布到随机分布到各个频率,并且对水印进行了加密。

img

(4)原图像频域。经历的是傅里叶变换,下图变换后的频域图像

img

(5)水印图像频域。经历的是傅里叶变换,下图变换后的频域图像

img

(6)合并水印和原图。之后,将叠加水印的频谱进行傅里叶逆变换,得到叠加数字水印后的图像,,将图像频域和水印编码进行合并。看不出来已经加了水印吧,哈哈哈

实际上,我们是把水印以噪声的形式添加到原图像中。

img

(7)水印图与原图的残差(看不出来残差区别,需要调整对比度才能看得出来)

img

(8)最终的均方差(MSE)和信噪比(PSNR)

img

(9)下图是原图频谱竖过来的样子,其能量主要集中在低频。

那么,为什么频谱发生了巨大的变化,而在空域却变化如此小呢?这是因为我们避开了图像的主要频率。

(10)水印提取是水印叠加的逆过程,

img

(11)提取后,得到水印。

img

复杂水印例子2:

https://www.zhihu.com/question/50735753/answer/883938990?utm_source=wechat_session&utm_medium=social&s_s_i=lfr5QBOrIigJgn8SrrpuNiPqJBC7fQLGofxMEOH0q%2FY%3D&s_r=1&from=singlemessage

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350