ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

图片隐写术了解下

2020-03-16 17:07:34  阅读:503  来源: 互联网

标签:canvas ctx 了解 rgba 隐写术 alpha showData data 图片


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

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,欢迎提出~~~~~~~~~~


 

标签:canvas,ctx,了解,rgba,隐写术,alpha,showData,data,图片
来源: https://www.cnblogs.com/edgeQAQ/p/12503919.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有