天天看點

将js/css腳本放到png圖檔中的實踐。

起因

進階浏覽器支援data協定,如:

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC"/>

<script src="data:text/javascript;base64,YWxlcnQoJ2h0dHA6Ly93ZWliby5jb20venN3YW5nJyk7"></script>

不光是<img>标簽,<script>、<style>、<iframe>也都支援data協定;

data協定可以表示圖檔、文本、音視訊等各種二進制資料

另html5的canvas除了繪制圖像,還能讀寫每一個像素值

一種把腳本存到圖像中的思路就有了。

經過

最初的嘗試就是把每個字元挨個放到像素的r、g、b、a裡

需要處理的問題:

1、計算圖檔的尺寸;

     本來想用高度為1,寬度為字元串長度的尺寸,但考慮到壓縮比和美觀,是以決定生成正方形。

        var pixel = Math.ceil((text.length + 3) / 4);

        var size = Math.ceil(Math.sqrt(pixel));

2、每個字元unicode編碼,可能會超出255;

     那就得将字元串轉成ascii碼

function encodeUTF8(str) {

    return String(str).replace(

        /[\u0080-\u07ff]/g,

        function(c) {

            var cc = c.charCodeAt(0);

            return String.fromCharCode(0xc0 | cc >> 6, 0x80 | cc & 0x3f);

        }

    ).replace(

        /[\u0800-\uffff]/g,

            return String.fromCharCode(0xe0 | cc >> 12, 0x80 | cc >> 6 & 0x3f, 0x80 | cc & 0x3f);

    );

}

3、還得考慮異步的問題

    var img = document.createElement('img');

    img.onload = function(){

        //...;

    }

第一輪demo做完,結果測試不符合預期。跟蹤發現:還原的資料和編碼的資料相差較大。

開始在懷疑是不是img标簽繪制到canvas變成了有損壓縮,如果真是這樣,那整個方案就是不可行;

堅持“不抛棄,不放棄”+定位問題最簡化的原則,逐漸縮小範圍,最終鎖定是由于alpha值影響還原。

解決方案就是:一個像素存放三個字元并将alpha固定為255。

第二輪demo測試符合預期。

總結

build流程:

1、字元串轉換成ascii碼;

2、建立足夠存儲空間的canvas;

3、将字元填入到像素中(忽略alpha值);

4、擷取data url;

     canvas.toDataURL("image/png");

5、存為png圖檔。

代碼示例

[html] ​​view plain​​ ​​copy​​

  1. <textarea id="base64"></textarea>  
  2. <script>  
  3. function encodeUTF8(str) {  
  4.     return String(str).replace(  
  5.         /[\u0080-\u07ff]/g,  
  6.         function(c) {  
  7.             var cc = c.charCodeAt(0);  
  8.             return String.fromCharCode(0xc0 | cc >> 6, 0x80 | cc & 0x3f);  
  9.         }  
  10.     ).replace(  
  11.         /[\u0800-\uffff]/g,  
  12.             return String.fromCharCode(0xe0 | cc >> 12, 0x80 | cc >> 6 & 0x3f, 0x80 | cc & 0x3f);  
  13.     );  
  14. }  
  15. function request(url, loaded) {  
  16.     var xmlhttp = new XMLHttpRequest();  
  17.     xmlhttp.onreadystatechange = function() {  
  18.         if (xmlhttp.readyState == 4)  
  19.             if (xmlhttp.status == 200)  
  20.                 loaded(xmlhttp);  
  21.     }  
  22.     xmlhttp.open("GET", url, true);  
  23.     xmlhttp.send();  
  24. void function(){  
  25.     var source = 'tangram-1.5.0.js';  
  26.     request(source, function(xmlhttp){  
  27.         var text = encodeUTF8(xmlhttp.responseText);  
  28.         var pixel = Math.ceil((text.length + 2) / 3); // 1一個像素存3個位元組,  
  29.         var size = Math.ceil(Math.sqrt(pixel));  
  30.         //console.log([text.length, pixel, size, size * size * 3]);  
  31.         var canvas = document.createElement('canvas');  
  32.         canvas.width = canvas.height = size;  
  33.         var context = canvas.getContext("2d"),  
  34.             imageData = context.getImageData(0, 0, canvas.width, canvas.height),  
  35.             pixels = imageData.data;  
  36.         for(var i = 0, j = 0, l = pixels.length; i < l; i++){  
  37.             if (i % 4 == 3) { // alpha會影響png還原  
  38.                 pixels[i] = 255;  
  39.                 continue;  
  40.             }  
  41.             var code = text.charCodeAt(j++);  
  42.             if (isNaN(code)) break;  
  43.             pixels[i] = code;  
  44.         context.putImageData(imageData, 0, 0);  
  45.         document.getElementById('base64').value = canvas.toDataURL("image/png");  
  46.     });  
  47. }();  
  48. </script>  

編譯結果

調用流程:

1、加載png;

2、将png原尺寸繪制到canvas中;

3、讀取像素中的字元串;

4、生成相應協定的data url使用。

  1.     var source = 'tangram-1.5.0.png';  
  2.     var img = document.createElement('img');  
  3.     img.onload = function(){  
  4.         canvas.width = img.width;  
  5.         canvas.height = img.height;  
  6.         var context = canvas.getContext("2d");  
  7.         context.drawImage(img, 0, 0);  
  8.         var imageData = context.getImageData(0, 0, canvas.width, canvas.height),  
  9.         var script = document.createElement('script');  
  10.         var buffer = [];  
  11.         for (var i = 0, l = pixels.length; i < l; i++) {  
  12.             if (i % 4 == 3) continue; // alpha會影響png還原  
  13.             if (!pixels[i]) break;  
  14.             buffer.push(String.fromCharCode(pixels[i]));  
  15.         script.src = 'data:text/javascript;charset=utf-8,' + encodeURIComponent(buffer.join(''));  
  16.         document.body.appendChild(script);  
  17.         script.onload = function(){  
  18.             alert(T.date.format(new Date, 'yyyy年M月d日'));  
  19.         img = null;  
  20.     img.src = source;  

優勢

1、壓縮率大(50%);

2、隐蔽性相對高;

     可以設計加密的腳本,用公鑰解鎖;

3、減少網絡請求;

     可以将多個圖檔、腳本放到一個png裡,囧;

4、一種二進制處理文本的思路。

劣勢

1、解碼會使用更多的cpu,導緻加載緩慢;

2、不支援低端浏覽器;

3、開發維護成本更高。

1、不具實戰性;

2、在資料加密傳輸方面可以近一步研究。

繼續閱讀