工具系列简单水印watermarkdom和算法水印频域方式图片合并实现

17次阅读

共计 11015 个字符,预计需要花费 28 分钟才能阅读完成。

一、简单水印(watermark-dom)

阿里巴巴内网的不可见水印用的是什么算法?
据说月饼事件截图的那位员工也被开除了?

下面的只是简单的加一个很浅的水印,实现起来很容易。

1、看看水印的效果

随便找一个网站,比如就找掘金的个人首页,

(1)F12 检查模式,

(2)在 console 里粘贴下面的代码,

(function(watermark){window.watermarkdivs=[];var loadMark=function(settings){var defaultSettings={watermark_txt:"text",watermark_x:20,watermark_y:20,watermark_rows:0,watermark_cols:0,watermark_x_space:50,watermark_y_space:50,watermark_color:'#000000',watermark_alpha:0.005,watermark_fontsize:'18px',watermark_font:'微软雅黑',watermark_width:150,watermark_height:100,watermark_angle:15,watermark_bg_alpha:0.5};if(arguments.length===1&&typeof arguments[0]==="object"){var src=arguments[0]||{};for(key in src){if(src[key]&&defaultSettings[key]&&src[key]===defaultSettings[key])continue;else if(src[key])defaultSettings[key]=src[key]}}var oTemp=document.createDocumentFragment();if(window.watermarkdivs&&window.watermarkdivs.length>0){document.body.removeChild(document.getElementById("otdivid"));window.watermarkdivs=[]}var page_width=Math.max(document.body.scrollWidth,document.body.clientWidth);var page_height=Math.max(document.body.scrollHeight,document.body.clientHeight);var otdiv=document.getElementById("otdivid");if(defaultSettings.watermark_cols==0||(parseInt(defaultSettings.watermark_x+defaultSettings.watermark_width*defaultSettings.watermark_cols+defaultSettings.watermark_x_space*(defaultSettings.watermark_cols-1))>page_width)){defaultSettings.watermark_cols=parseInt((page_width-defaultSettings.watermark_x+defaultSettings.watermark_x_space)/(defaultSettings.watermark_width+defaultSettings.watermark_x_space));defaultSettings.watermark_x_space=parseInt((page_width-defaultSettings.watermark_x-defaultSettings.watermark_width*defaultSettings.watermark_cols)/(defaultSettings.watermark_cols-1))}if(defaultSettings.watermark_rows==0||(parseInt(defaultSettings.watermark_y+defaultSettings.watermark_height*defaultSettings.watermark_rows+defaultSettings.watermark_y_space*(defaultSettings.watermark_rows-1))>page_height)){defaultSettings.watermark_rows=parseInt((defaultSettings.watermark_y_space+page_height-defaultSettings.watermark_y)/(defaultSettings.watermark_height+defaultSettings.watermark_y_space));defaultSettings.watermark_y_space=parseInt(((page_height-defaultSettings.watermark_y)-defaultSettings.watermark_height*defaultSettings.watermark_rows)/(defaultSettings.watermark_rows-1))}var x;var y;for(var i=0;i<defaultSettings.watermark_rows;i++){y=defaultSettings.watermark_y+(defaultSettings.watermark_y_space+defaultSettings.watermark_height)*i;for(var j=0;j<defaultSettings.watermark_cols;j++){x=defaultSettings.watermark_x+(defaultSettings.watermark_width+defaultSettings.watermark_x_space)*j;var mask_div=document.createElement('div');mask_div.id='mask_div'+i+j;mask_div.appendChild(document.createTextNode(defaultSettings.watermark_txt));mask_div.style.webkitTransform="rotate(-"+defaultSettings.watermark_angle+"deg)";mask_div.style.MozTransform="rotate(-"+defaultSettings.watermark_angle+"deg)";mask_div.style.msTransform="rotate(-"+defaultSettings.watermark_angle+"deg)";mask_div.style.OTransform="rotate(-"+defaultSettings.watermark_angle+"deg)";mask_div.style.transform="rotate(-"+defaultSettings.watermark_angle+"deg)";mask_div.style.visibility="";mask_div.style.position="absolute";mask_div.style.left=x+'px';mask_div.style.top=y+'px';mask_div.style.overflow="hidden";mask_div.style.zIndex="9";mask_div.style.pointerEvents="none";mask_div.style.opacity=defaultSettings.watermark_alpha;mask_div.style.fontSize=defaultSettings.watermark_fontsize;mask_div.style.fontFamily=defaultSettings.watermark_font;mask_div.style.color=defaultSettings.watermark_color;mask_div.style.textAlign="center";mask_div.style.width=defaultSettings.watermark_width+'px';mask_div.style.height=defaultSettings.watermark_height+'px';mask_div.style.display="block";mask_div.style.fontWeight="900";oTemp.appendChild(mask_div)}};document.body.appendChild(oTemp)};watermark.load=function(settings){window.onload=function(){loadMark(settings)};window.onresize=function(){loadMark(settings)}};watermark.load({watermark_txt:" 测试水印,saucxs,测试水印,songEagle,工号等 "})})(window.watermark={});

(3)改变一下页面窗口大小,然后就可以看到我首页出现如下图的水印,一层浅浅的水印

它的作用是在当前页面上增加了一个透明度只有 0.005 的很多的水印。水印内容“测试水印,saucxs,测试水印,songEagle,工号等”。

2、水印的优化

当然是需要使用者不知道这个页面有水印,保证一些信息的安全性以及泄露之后可以追踪到是谁在泄露机密信息,他没有发觉到有水印,所以需要将水印调成透明度很低,这样使用者看不到水印,但是一旦使用者截图将图片发布到互联网上,这时候只需要将图片进行一些简单操作就可以让水印重新显现出来。

只要使用者不知道有水印存在,这样就是从根本上加了水印,信息的源头上加了水印,确保信息的安全,以及泄露之后的追踪。

我们还是拿掘金的个人首页作为试验田。这回我们将水印的透明度调成 0.004,水印字体颜色调成页面背景颜色(掘金的是 #f4f5f5),然后截图(这回看不出来有水印吧)

把图片放到 PS,我使用的是 ps cs6 里面,建一个空白图层在上面,填充为黑色,操作:如图所示背景色选择黑色,然后按 shift+f5,选择背景色进行填充。

混合模式选择正片叠底这一类的(也就是让亮的更亮,暗的更暗),一个个试。当我试到“实色混合”和“颜色加深”的时候,水印就显示出来了。

哇,吓到我了,原来可以这么玩。

3、watermark-dom 水印原理分析

通过 js 向 html 中一次性添加 dom 元素,所以我取名叫做 watermark-dom,如果一旦知道有这个水印插件,使用者是可以手动将当前页面的水印 dom 删掉,这样也就是开发人员知道怎么弄,对于其他人员还是不知道如何去掉页面水印的。

3.1 关键元素 1 –pointer-events:none

这个归功于 css 的属性,准确的说是 css3 的一个属性 pointer-events,本来这个属性的而设计之初是为了 – 真正意义上的禁用元素,因为设置值为 none 的时候,这个元素是使用鼠标或者触摸感知不到的,可以称 pointer-events 为“元素虚化”。

支持浏览器:目前 FireFox 浏览器,Chrome 都支持。Opera 以及 IE 不支持。

该属性的缺点:

1、pointer-events:none 影响触屏设备的滚动,如无线端页面等;

2、如果子元素设置了 pointer-eventes: auto 会导致滚动的时候页面闪动

3.2 关键元素 2 –opacity 属性

为什么这么说 opacity 属性,因为这个属性是对元素进行透明的设置,因为是水印,希望尽量不影响到正常页面的视觉体验。

支持浏览器:所有浏览器都支持 opacity 属性。注意:IE8 以及更早的版本支持替代的 filter 属性。例如:filter:Alpha(opacity=50)。

在设置这个透明度的时候,经过测试发现,水印透明度,要求设置在大于等于 0.005,因为比这个低的话,使用 ps 显现的时候效果不明显。

如果需要去尽量影藏水印,可以把水印的字体颜色和页面的背景颜色调成一致。

如果就要正常显示,尽量设置 opactity 的时候设置为大于等于 0.005,一个性能和显示的平衡点。

4、附上 watermark-dom 完整代码

如果后续更新和优化的时候,源码请看这个 watermark-dom 里的 watermark.js 文件

5、一个具体的例子

引入这个 watermark.js 的代码。

html 代码

<html>
    <meta charset="utf-8">
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <script type="text/javascript" src="watermark.js"></script>
    <script type="text/javascript">
        watermark.load({watermark_txt: "测试水印,1021002301,测试水印,SDAHJDBJJdjsfsc"});
    </script>
    
    <body>
        <div style="width:300px;height:300px;background-color: red; opacity:0.98;" onclick="alert(1);">test</div>
        <div style="width:300px;height:300px;background-color: blue;  opacity:0.9;" onclick="alert(2);">test</div>
        <a href="www.test.com"> baidu</a>
    </body>
</html>

效果如下图:

6、水印测试工具

写了插件,这个是测试地址

watermark-dom 的测试工具

包括,测试,重置,显示,随机,四个部分。

特性:1、测试对水印参数属性,重置水印属性参数,显示此时的水印属性参数,随机产生水印属性参数;

2、水印按钮组是 position 值 fixed,可以浮现在页面之上,不占字节。

3、对系统的各个部分页面进行水印的测试。

7、基础使用

7.1 本地引入封装的 js 文件

只是简单的加一个很浅的水印,实现起来很容易。不需要引入 jquery 插件。

watermark.js 是必须要引进的组件

第一步:获取组件方式:git clone https://github.com/saucxs/watermark-dom.git

第二步:clone 后,在需要加水印的相关页面引入水印文件 ”watermark.js”:

script type="text/javascript" src="./watermark.js"></script>

第三步:在确保页面 DOM 加载完毕之后,调用 watermark 的 load 方法 (手动加载):

   <script>watermark.load({watermark_txt: "测试水印,1021002301,测试水印,100101010111101"})</script>

注意:我们提供了 init 方法,用来初始化水印,添加 load 和 resize 事件

   <script>watermark.init({watermark_txt: "测试水印,1021002301,测试水印,100101010111101"})</script>

7.2 本地引入封装的 js 文件

第一步:npm 获取水印组件包:

npm install watermark-dom

第二步:引入水印模块:

import watermark from 'watermark-dom'
或者
var watermarkDom = require("watermark-dom")

第三步:在确保页面 DOM 加载完毕之后,调用 watermark 的 load 方法 (手动加载):

   <script>watermark.load({watermark_txt: "测试水印,1021002301,测试水印,100101010111101"})</script>

注意:我们提供了 init 方法,用来初始化水印,添加 load 和 resize 事件

   <script>watermark.init({watermark_txt: "测试水印,1021002301,测试水印,100101010111101"})</script>

8、支持各种属性配置使用

watermark_id: 'wm_div_id',          // 水印总体的 id
watermark_prefix: 'mask_div_id',    // 小水印的 id 前缀
watermark_txt:"测试水印",             // 水印的内容
watermark_x:20,                     // 水印起始位置 x 轴坐标
watermark_y:20,                     // 水印起始位置 Y 轴坐标
watermark_rows:0,                   // 水印行数
watermark_cols:0,                   // 水印列数
watermark_x_space:100,              // 水印 x 轴间隔
watermark_y_space:50,               // 水印 y 轴间隔
watermark_font:'微软雅黑',           // 水印字体
watermark_color:'black',            // 水印字体颜色
watermark_fontsize:'18px',          // 水印字体大小
watermark_alpha:0.15,               // 水印透明度,要求设置在大于等于 0.005
watermark_width:100,                // 水印宽度
watermark_height:100,               // 水印长度
watermark_angle:15,                 // 水印倾斜度数
watermark_parent_width:0,      // 水印的总体宽度(默认值:body 的 scrollWidth 和 clientWidth 的较大值)watermark_parent_height:0,     // 水印的总体高度(默认值:body 的 scrollHeight 和 clientHeight 的较大值)watermark_parent_node:null     // 水印插件挂载的父元素 element, 不输入则默认挂在 body 上 

上面的属性都支持配置的,怎么使用呢?

基本山需要自己配置的属性:watermark_txt,watermark_color,watermark_fontsize,watermark_alpha,watermark_anglewatermark_width,watermark_height 这 7 个属性一般是经常用到的,其他属性一般用的偏少。需要用到的就设置一下,不需要用到的就可以不设置,插件内部会有默认值的。

 watermark.load({ 
    watermark_txt:"测试水印,saucxs,测试水印,songEagle,工号等",  // 水印的内容
    watermark_color:'#5579ee',            // 水印字体颜色
    watermark_fontsize:'24px',          // 水印字体大小
    watermark_alpha:0.5,               // 水印透明度,要求设置在大于等于 0.005
    watermark_angle:135,                 // 水印倾斜度数
    watermark_width:200,                // 水印宽度
    watermark_height:200,               // 水印长度
});

这个设置之后后的页面如下图所示:

所以一般先在 watermark-dom 的测试工具上,把需要配置的属性值,调试好之后在写入代码中,这样效率更高。

二、复杂(频域方式图片合并水印)

频域方式图片合并水印,意思就是说,从图片编码里进行水印的添加,这样从最根本上解决图片的水印,而且水印的形成之后的图片是用肉眼和 PS 显示不出来的,只有通过反向编码让水印显示,这样就算是开发人员也不会知道这个图片是否含有水印,只有开发这个系统的人员知道。

1、原理分析

编码的目的有两个:

1、对水印加密,

2、二控制水印能量的分布。

以下是频域方式图片合并水印的实验。

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

(2)水印照片。

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

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

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

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

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

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

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

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

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

合并之后

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

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

2、附上可以还原实验的全部代码(matlab 代码)

clc;clear;close all;
alpha = 1;

%% read data
im = double(imread('G:\2017 学习 \Work\ 图片水印 \test.jpg'))/255;
mark = double(imread('G:\2017 学习 \Work\ 图片水印 \watermark.png'))/255;
figure, imshow(im),title('original image');
figure, imshow(mark),title('watermark');

%% encode mark
imsize = size(im);
%random
TH=zeros(imsize(1)*0.5,imsize(2),imsize(3));
TH1 = TH;
TH1(1:size(mark,1),1:size(mark,2),:) = mark;
M=randperm(0.5*imsize(1));
N=randperm(imsize(2));
save('G:\2017 学习 \Work\ 图片水印 \encode.mat','M','N');
for i=1:imsize(1)*0.5
    for j=1:imsize(2)
        TH(i,j,:)=TH1(M(i),N(j),:);
    end
end
% symmetric
mark_ = zeros(imsize(1),imsize(2),imsize(3));
mark_(1:imsize(1)*0.5,1:imsize(2),:)=TH;
for i=1:imsize(1)*0.5
    for j=1:imsize(2)
        mark_(imsize(1)+1-i,imsize(2)+1-j,:)=TH(i,j,:);
    end
end
figure,imshow(mark_),title('encoded watermark');

%% add watermark
FA=fft2(im);
figure,imshow(FA);title('spectrum of original image');
FB=FA+alpha*double(mark_);
figure,imshow(FB); title('spectrum of watermarked image');
FAO=ifft2(FB);
figure,imshow(FAO); title('watermarked image');
%imwrite(uint8(FAO),'watermarked image.jpg');
RI = FAO-double(im);
figure,imshow(uint8(RI)); title('residual');
%imwrite(uint8(RI),'residual.jpg');
xl = 1:imsize(2);
yl = 1:imsize(1);
[xx,yy] = meshgrid(xl,yl);
figure, plot3(xx,yy,FA(:,:,1).^2+FA(:,:,2).^2+FA(:,:,3).^2),title('spectrum of original image');
figure, plot3(xx,yy,FB(:,:,1).^2+FB(:,:,2).^2+FB(:,:,3).^2),title('spectrum of watermarked image');
figure, plot3(xx,yy,FB(:,:,1).^2+FB(:,:,2).^2+FB(:,:,3).^2-FA(:,:,1).^2+FA(:,:,2).^2+FA(:,:,3).^2),title('spectrum of watermark');

%% extract watermark
FA2=fft2(FAO);
G=(FA2-FA)/alpha;
GG=G;
for i=1:imsize(1)*0.5
    for j=1:imsize(2)
        GG(M(i),N(j),:)=G(i,j,:);
    end
end
for i=1:imsize(1)*0.5
    for j=1:imsize(2)
        GG(imsize(1)+1-i,imsize(2)+1-j,:)=GG(i,j,:);
    end
end
figure,imshow(GG);title('extracted watermark');
%imwrite(uint8(GG),'extracted watermark.jpg');

%% MSE and PSNR
C=double(im);
RC=double(FAO);
MSE=0; PSNR=0;
for i=1:imsize(1)
    for j=1:imsize(2)
        MSE=MSE+(C(i,j)-RC(i,j)).^2;
    end
end
MSE=MSE/360.^2;
PSNR=20*log10(255/sqrt(MSE));
MSE
PSNR

三、总结

1、watermark-dom 水印是一个简单的用于 dom 的水印,简单易上手,支持多选项配置。

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

正文完
 0