在座的各位大大,今天天气晴朗哈,楼下这位即将给大家表演下“隐身术”,大家有钱捧个钱场,没钱的先去赚钱再来捧场哈 经法院判定,证据确凿,大众点评所属公司最后向美团所属公司赔付49400元 证据: 难道美团在图片上加了水印? 大众点评睁一只眼闭一只眼拿去用了? 事情还真是这么个回事,但是大众点评绝对不是"睁一只眼,闭一只眼" 而是因为美团在自己的图片上加了隐写术水印,肉眼看不出来哦 下面是博主敲代码的机子,博主早就看这个机子不舒服了,用娱乐大师测了下,心里“mmp” 但当原形毕露时:
先贴上代码: <div class="wrap"> <canvas id="canvas" width="546" height="366"></canvas> </div> 然后贴出核心处理代码 // 加密 var ctx = document.getElementById('canvas').getContext('2d'); var img = new Image(); var hideDate; // 定义隐藏数据,这里文案就是想要隐藏的数据 var showData; // 定义展示数据,图片是想要展示的数据 ctx.font = '40px Microsoft Yahei'; ctx.fillText('我想换显卡', 150, 240); // 获取隐藏文字的数据 hideDate = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height).data; // 定义隐藏数据赋值 img.onload = function() { ctx.drawImage(img, 0, 0); showData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height); makeHide(hideDate, 'R'); }; img.src = "../img/diy.png"; // 加密方法 function makeHide(newData, color) { var bit, alpha; // alpha的作用是找到alpha通道值, switch(color){ case 'R': bit = 0; alpha = 3; break; case 'G': bit = 1; alpha = 2; break; case 'B': bit = 2; alpha = 1; break; } for(var i = 0; i < showData.data.length; i ){ if(i % 4 == bit){ // 没有文字信息的像素 if(newData[i alpha] == 0 && (showData.data[i] % 2 == 1)){ showData.data[i]--; // 为何将非文字区域R像素的奇数-1转化为偶数? 1、改变小看不出,2、解密时R值为偶数直接归0,配合其对应rgba组的其他值归0呈现全黑色,(虽然自加也能够使得奇数变偶数,但是255 就会超出,所以这边必须使用自减) // 有信息的像素 } else if (newData[i alpha] != 0 && (showData.data[i] % 2 == 0)){ showData.data[i] ; // 为何将文字所对应图片区域的R像素的偶数 1转化为奇数? 1、改变小看不出,2、解密时可以将文字区域R值直接转化为255,(同理这边不能使用--) } } } ctx.putImageData(showData, 0, 0); }
没错,getImageData能够获取canvas绘制图片的宽高信息,还能获取到data 也就是Uint8ClampedArray的数据。 那么 Uint8ClampedArray 是什么呢? 度娘告诉我们,它是8位无符号整型固定数组,表示一个由值固定在0-255区间的8位无符号整型组成的数组 所以showData是canvas上图片的rgba信息,那么hideData则是只写了文案的canvas 的rgba信息, hideData一样具备799344个rgba数组元素信息,只不过绝大部分是透明的canvas像素点,这点需要理解哦! 接下来,我们就需要对这两组rgba进行“偷梁换柱” 所以先埋个问题: 我们想把文案放到图片上,那是不是等同于把hideData上记载着文案的rgba值和 var bit, alpha; // alpha的作用是找到alpha通道值, switch(color){ case 'R': bit = 0; alpha = 3; break; case 'G': bit = 1; alpha = 2; break; case 'B': bit = 2; alpha = 1; break; } 我们定义了bit,和alpha两个变量,那么这两个变量用来干嘛的呢? 我们先把hideData中的Uint8ClampedArray打印出来看下 也就意味着,我们需要隐藏的文案,可以通过alpha透明度是否为0来判断,也就是非透明~ 然后我们进行下一步,这里的newData参数传的就是hideData,我们操作的是R通道,因此 switch(color){ case 'R': bit = 0; alpha = 3; break; case 'G': bit = 1; alpha = 2; break; case 'B': bit = 2; alpha = 1; break; } for(var i = 0; i < showData.data.length; i ){ if(i % 4 == bit){ // 没有文字信息的像素 if(newData[i alpha] == 0 && (showData.data[i] % 2 == 1)){ showData.data[i]--; // 为何将图片区域R通道的奇数自减1转化为偶数? (我们这边以R通道为例) 其实我们对所需的rgba值进行自增自减属于自己制定的法则,为的就是在解密时能够确定处理那些rgba值, // 解密 var ctx = document.getElementById('canvas').getContext('2d'); var img = new Image(); var showData; img.onload = function() { ctx.drawImage(img, 0, 0); // 获取指定区域的canvas像素信息 showData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height); getShow(showData, 'R'); }; img.src = '../img/d_r.png'; // d_r.png图片是隐写术后的canvas右键保存下来的 function getShow(showData, color) { var colorNum; switch(color){ case 'R': colorNum = 0; break; case 'G': colorNum = 1; break; case 'B': colorNum = 2; break; } for(var i = 0; i < showData.data.length; i ){ // 红/绿/蓝色分量判断 if(i % 4 == colorNum){ if(showData.data[i] % 2 == 0){ showData.data[i] = 0; } else { showData.data[i] = 255; } } else if(i % 4 == 3){ // alpha透明度不改变 continue; } else { // rgba组的其他值归0,替换掉,干扰像素的颜色,背景置黑,不替换更好看 // showData.data[i] = 0; } } // 将结果绘制到画布 ctx.putImageData(showData, 0, 0); } 解开隐写术其实是一个反向的操作~~~ |
|