图片隐写术了解下

在座的各位大大,今天天气晴朗哈,楼下这位即将给大家表演下“隐身术”,大家有钱捧个钱场,没钱的先去赚钱再来捧场哈

biu~~  biu~~  biu~~

图片隐写术了解下

哇塞,gif, 江湖骗术~~~~~  拉出去砍了!!!

今天呢,我们也用前端玩玩这个“隐身术”, 不过呢,在前端,这个技术大多称为"隐写术"

接下来,我们就通过一个例子,来了解下“图片隐写术”

案件:(大概是这样的)
  
2013年年底,美团起诉大众点评盗用其平台上部分摄影图片

  经法院判定,证据确凿,大众点评所属公司最后向美团所属公司赔付49400元
  
  那么关于这个案件具体起因经过结果是如何的,大家可以多问问度娘,我们今天来聊聊这个“证据确凿”

 

证据:
  
美团在图片上留下了证据,大众点评盗用图片踩了坑

   难道美团在图片上加了水印? 大众点评睁一只眼闭一只眼拿去用了?

  事情还真是这么个回事,但是大众点评绝对不是"睁一只眼,闭一只眼"

   而是因为美团在自己的图片上加了隐写术水印,肉眼看不出来哦

   我们举个栗子:

   下面是博主敲代码的机子,博主早就看这个机子不舒服了,用娱乐大师测了下,心里“mmp”
   于是就抱怨了下,用隐写术写下了“我想换显卡”这句话,处理后肉眼看不出图片上有任何字体
   于是乎,隐写术处理后:

   图片隐写术了解下

 

    但当原形毕露时:


    图片隐写术了解下 
  
   那么是如何将文字写入图片之中,且隐藏起来的呢?
   canvas rgba 有话要说!!!
   图片隐写术了解下
  
  我们都知道,在啊html中,img标签可以展示图片,canvas也可以将图片展示出来,canvas是基于像素的图片API,简单说就是
能够把图片一像素一像素地绘制出来。也可以理解为一个超级牛的画家,在空白的canvas画板上绘制了一幅画,而这幅画和你的img
一模一样~~~~ 
  然后rgba听够了canvas的吹嘘,早已难以忍受
  图片隐写术了解下
  rgba自称,你们看到的五颜六色,色彩斑然,那不都是来源于我的功劳?
  我有R, G, B 对应红绿蓝三种颜色通道,还有A [alpha(阿尔法)]控制透明度,我才是绘制图片的大神!!!

  博主只好劝架了,
  图片隐写术了解下
  今天就合作一下,一起搞“图片隐写术”
  

先贴上代码:
  构建一个canvas画布,宽高根据你要处理的图片来~~~

<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);
        }    


接下来我们一步步讲解下
  首先我们要先了解下canvas的 getImageData 这个API,不妨将上面的showData打印出来,我们得到如下结果:
  图片隐写术了解下

 

 

 

没错,getImageData能够获取canvas绘制图片的宽高信息,还能获取到data 也就是Uint8ClampedArray的数据。

那么 Uint8ClampedArray 是什么呢?

度娘告诉我们,它是8位无符号整型固定数组,表示一个由值固定在0-255区间的8位无符号整型组成的数组
额~~~em,em,em~~~,听着好像有点懂,又有点麻烦的样子~~~ 
要不这样吧,我们且看它所说的0-255, 这不就是rgba中各个通道值的范围吗? 于是我们明白,这个数组存储的就是这张照片的
每一个像素rgba的信息,为什么说是每一个像素呢? 
我们发现数组后面有一个数字,799344,然后我们这样计算
546(width) * 366(height) * 4(每一个像素有r,g,b,a4种值)  = 799344 
最终能证实我们的猜想是对的,我们也可以将其展开,可以看到里面确实存储着这张图片你的所有rgba信息


图片隐写术了解下

 

所以showData是canvas上图片的rgba信息,那么hideData则是只写了文案的canvas 的rgba信息,

hideData一样具备799344个rgba数组元素信息,只不过绝大部分是透明的canvas像素点,这点需要理解哦!
没错,它也是一个546 * 366的canvas
图片隐写术了解下

接下来,我们就需要对这两组rgba进行“偷梁换柱”

所以先埋个问题:  我们想把文案放到图片上,那是不是等同于把hideData上记载着文案的rgba值和
showData位置rgba值进行处理? 
因此,我们对两者的Uint8ClampedArray数组进行操作:

由于图片有R, G, B三个颜色通道,我们任选其一进行操作,博主我选择R,红色,象征着~~~~~ 

我们回归主要的方法函数 makeHide 

            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两个变量,那么这两个变量用来干嘛的呢? 
主要作用还是确定R,G,B,A的值在数组中的索引
我们知道rgba每一组有4个值,但是Uint8ClampedArray存储的却是一维数组,也就是4个值4个值一直拼接在一起
因此R通道在每一组rgba值中的索引是0,G通道索引为1, B通道索引为2

那alpha呢? 定义的alpha是要干嘛的呢?  这里很重要,这里相当重要,这里极其重要!!!

我们先把hideData中的Uint8ClampedArray打印出来看下
图片隐写术了解下

 

 

也就意味着,我们需要隐藏的文案,可以通过alpha透明度是否为0来判断,也就是非透明~

然后我们进行下一步,这里的newData参数传的就是hideData,我们操作的是R通道,因此
bit = 0, alpha = 3,然后接下来的通道颜色自增自减就需要各位脑洞打开去想象~~~ em, em, em 确实要靠想象一下

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通道为例)
                    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);

其实我们对所需的rgba值进行自增自减属于自己制定的法则,为的就是在解密时能够确定处理那些rgba值,
所以这边的处理方法,各位大大不妨脑洞大开,还有别的方式

接下来贴出解密代码,一样以R通道为例子,记得事先把隐写好的图片右键保存下来,解密时可以出场~

// 解密
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);
}

解开隐写术其实是一个反向的操作~~~
大家不妨试下,这里理解rgba通道的处理确实需要一些想象~~~ 
  图片隐写术了解下

如果有什么问题,或者发现其中的不足和BUG,欢迎提出~~~~~~~~~~


 

上一篇:博客园自定义页面


下一篇:修改 浏览器滚动轴样式