将js/css脚本放到png图片中的实践。
起因
高级浏览器支持data协议,如:
<imgsrc="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC"/>
<scriptsrc="data:text/javascript;base64,YWxlcnQoJ2h0dHA6Ly93ZWliby5jb20venN3YW5nJyk7"></script>
不光是<img>标签,<script>、<style>、<iframe>也都支持data协议;
data协议可以表示图片、文本、音视频等各种二进制数据
另html5的canvas除了绘制图像,还能读写每一个像素值
一种把脚本存到图像中的思路就有了。
经过
最初的尝试就是把每个字符挨个放到像素的r、g、b、a里
需要处理的问题:
1、计算图片的尺寸;
本来想用高度为1,宽度为字符串长度的尺寸,但考虑到压缩比和美观,所以决定生成正方形。
varpixel=Math.ceil((text.length+3)/4);
varsize=Math.ceil(Math.sqrt(pixel));
2、每个字符unicode编码,可能会超出255;
那就得将字符串转成ascii码
functionencodeUTF8(str){
returnString(str).replace(
/[\u0080-\u07ff]/g,
function(c){
varcc=c.charCodeAt(0);
returnString.fromCharCode(0xc0|cc>>6,0x80|cc&0x3f);
}
).replace(
/[\u0800-\uffff]/g,
function(c){
varcc=c.charCodeAt(0);
returnString.fromCharCode(0xe0|cc>>12,0x80|cc>>6&0x3f,0x80|cc&0x3f);
}
);
}
3、还得考虑异步的问题
varimg=document.createElement('img');
img.onload=function(){
//...;
}
第一轮demo做完,结果测试不符合预期。跟踪发现:还原的数据和编码的数据相差较大。
开始在怀疑是不是img标签绘制到canvas变成了有损压缩,如果真是这样,那整个方案就是不可行;
坚持“不抛弃,不放弃”+定位问题最简化的原则,逐步缩小范围,最终锁定是由于alpha值影响还原。
解决方案就是:一个像素存放三个字符并将alpha固定为255。
解决方案就是:一个像素存放三个字符并将alpha固定为255。
第二轮demo测试符合预期。
总结
build流程:
1、字符串转换成ascii码;
2、创建足够存储空间的canvas;
3、将字符填入到像素中(忽略alpha值);
4、获取data url;
canvas.toDataURL("image/png");
5、存为png图片。代码示例
<textarea id="base64"></textarea>
<script>
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,
function(c) {
var cc = c.charCodeAt(0);
return String.fromCharCode(0xe0 | cc >> 12, 0x80 | cc >> 6 & 0x3f, 0x80 | cc & 0x3f);
}
);
}
function request(url, loaded) {
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4)
if (xmlhttp.status == 200)
loaded(xmlhttp);
}
xmlhttp.open("GET", url, true);
xmlhttp.send();
}
void function(){
var source = 'tangram-1.5.0.js';
request(source, function(xmlhttp){
var text = encodeUTF8(xmlhttp.responseText);
var pixel = Math.ceil((text.length + 2) / 3); // 1一个像素存3个字节,
var size = Math.ceil(Math.sqrt(pixel));
//console.log([text.length, pixel, size, size * size * 3]);
var canvas = document.createElement('canvas');
canvas.width = canvas.height = size;
var context = canvas.getContext("2d"),
imageData = context.getImageData(0, 0, canvas.width, canvas.height),
pixels = imageData.data;
for(var i = 0, j = 0, l = pixels.length; i < l; i++){
if (i % 4 == 3) { // alpha会影响png还原
pixels[i] = 255;
continue;
}
var code = text.charCodeAt(j++);
if (isNaN(code)) break;
pixels[i] = code;
}
context.putImageData(imageData, 0, 0);
document.getElementById('base64').value = canvas.toDataURL("image/png");
});
}();
</script>
编译结果
调用流程:
1、加载png;
2、将png原尺寸绘制到canvas中;
3、读取像素中的字符串;
4、生成相应协议的data url使用。
代码示例
<script> void function(){ var source = 'tangram-1.5.0.png'; var img = document.createElement('img'); img.onload = function(){ var canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; var context = canvas.getContext("2d"); context.drawImage(img, 0, 0); var imageData = context.getImageData(0, 0, canvas.width, canvas.height), pixels = imageData.data; var script = document.createElement('script'); var buffer = []; for (var i = 0, l = pixels.length; i < l; i++) { if (i % 4 == 3) continue; // alpha会影响png还原 if (!pixels[i]) break; buffer.push(String.fromCharCode(pixels[i])); } script.src = 'data:text/javascript;charset=utf-8,' + encodeURIComponent(buffer.join('')); document.body.appendChild(script); script.onload = function(){ alert(T.date.format(new Date, 'yyyy年M月d日')); } img = null; } img.src = source; }(); </script>
优势
代码示例
<script> void function(){ var source = 'tangram-1.5.0.png'; var img = document.createElement('img'); img.onload = function(){ var canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; var context = canvas.getContext("2d"); context.drawImage(img, 0, 0); var imageData = context.getImageData(0, 0, canvas.width, canvas.height), pixels = imageData.data; var script = document.createElement('script'); var buffer = []; for (var i = 0, l = pixels.length; i < l; i++) { if (i % 4 == 3) continue; // alpha会影响png还原 if (!pixels[i]) break; buffer.push(String.fromCharCode(pixels[i])); } script.src = 'data:text/javascript;charset=utf-8,' + encodeURIComponent(buffer.join('')); document.body.appendChild(script); script.onload = function(){ alert(T.date.format(new Date, 'yyyy年M月d日')); } img = null; } img.src = source; }(); </script>
优势
1、压缩率大(50%);
2、隐蔽性相对高;
可以设计加密的脚本,用公钥解锁;
3、减少网络请求;
可以将多个图片、脚本放到一个png里,囧;
4、一种二进制处理文本的思路。
劣势
1、解码会使用更多的cpu,导致加载缓慢;
2、不支持低端浏览器;
3、开发维护成本更高。
总结
1、不具实战性;
2、在数据加密传输方面可以近一步研究。
相关demo