利用CANVAS进行文件压缩传输解析是否可行

一、背景简介

本文的思路来源于Jacob Seidelin在其个人博客上于2008年5月4日写的一篇《Compression using Canvas and PNG-embedded data》,虽然至今已时隔四年,但随着HTML5及其相关技术的普及兴起,这篇文章近期才被国内一些研究机构提起。文中描述了一种利用canvas的getImageData来实现特殊的文件压缩传输的方法,其测试文件选用了一系列JS脚本文件,为了对比这种方法的实际效率,我们先来看看传统的JS文件压缩传输的方法。

二、JS文本传统压缩方法

1、文本删减

对于脚本文件,只要它能在客户端正确执行,那些为了在编写测试过程中使语句之间结构清晰的注释、空行、空格等等一系列书写都是可以去除的,这就构成了文本删减的理论依据。

下面以压缩工具ECMAScript Cruncher为例来说明文本删减的压缩能力:


通过压缩比就可以看到文本删减的能力是极其有限的,但其确实是一种广泛使用的技术。另外有一点值得注意,ECMAScript Cruncher的LEVEL4会自动精简修改变量名,这对于那些全局变量来说,极有可能造成程序不能正确运行,故LEVEL4需谨慎使用或者有针对性的优化修改发行文件以保证程序正确运行。

2、GZIP压缩传输

通过设置content-encoding = gzip可以直接传输压缩格式,例如使用GZIP.EXE将jquery.js压缩成jquery.js.gz然后再进行传输。其使用前提有两点,服务器有相应的进行压缩处理的能力(当然也可以预先将压缩文件准备好),另外需要有支持解析GZIP的客户端(目前的浏览器基本都能支持)。

我们来大致了解一下GZIP压缩方式到底做了些什么。GZIP压缩方式首先使用了LZ77压缩算法,LZ77算法使用了滑动窗口缓存的技术,对于近期窗口内重复出现的字符可用较短的编码替代,对单个或短群字符有较好的压缩效率。另外在LZ77压缩算法之后,GZIP还使用了经典的Huffman压缩算法,大大减小了文件的熵。

3、文本删减和GZIP压缩综合测试对比

本文中的所用的测试文件均为1.7.2版的Jquery的development版本


Jquery给出了两个版本供我们下载使用,一个是247KB的的DEVELOPMENT版本(未压缩),另一个是32KB的PRODUCTION版本(经过了最小化以及GZIP压缩,压缩比较开发版达到了7.719)

我们先对源文件直接进行文本删减和GZIP压缩,看看各自的压缩能力。

文本删减结果:


GZIP压缩结果:


可以明显看到利用到熵编码的GZIP压缩方式有非常明显的优势,下面我们将经过文本删减的文件再进行GZIP压缩,看看两种方法综合运用的效果:


注意到一个有趣的现象,随着文本删减等级的提高,相应的GZIP的压缩比逐步下降,这说明文本删减在一定程度上减小了文本自身的信息冗余,比如连续的空格。最终采用LEVEL4+GZIP的方案的生成文件大小为35.4KB,十分接近官方优化后的32KB发行版本。

目前对于文本传输基本就采用以上两种方法,可以单独使用或者结合混用。

三、利用CANVAS的相关技术进行压缩

1、canvas简介

canvas作为HTML5重点宣传的的对象,其作用是用于提高页面的现实效果,丰富页面的内容,是FLASH技术的一个强大对手。使用上其只是一个标签,并运用javascript调用相关接口来实现绚丽的画面。可以说从一开始canvas的作用就和数据压缩没有任何关系,在此我不得不对Jacob Seidelin的独特想法进行称赞。

2、基本思想

这一压缩途径主要依赖于canvas的一个功能函数getImageData,这个函数可以获取canvas画布上的选定位置的图像全部数据。Jacob Seidelin的思路分为以下几步:

(1)、首先将要传输的文件压缩成图像文件,为了能够无误的恢复,这个图像文件必须是无损的,故不能采用JPEG这样的图像文件类型。经过选择,作者选取了PNG格式作为文件传输格式。将文本转换为图片的方法很简单,即逐字符将ASCII码作为值写入到每个像素的RGB中,最后的生成的图像虽然视觉上毫无意义,但是它包含了一个文本的全部数据。

(2)、将图像文件传输到客户端后,将文件绘制到CANVAS画布,利用getImageData读取画布中每个像素的信息,实际上就是在读取文本文件的信息。

3、代码分析与修改

我们先来看看Jacob Seidelin给出的将文本转化为图片的代码:

<?
$filename = "prototype-1.6.0.2.packed.js";
if (file_exists($filename)) {
	$iFileSize = filesize($filename);
	$iWidth = ceil(sqrt($iFileSize / 1));
	$iHeight = $iWidth;
	$im = imagecreatetruecolor($iWidth, $iHeight);
	$fs = fopen($filename, "r");
	$data = fread($fs, $iFileSize);
	fclose($fs);
	$i = 0;
	for ($y=0;$y<$iHeight;$y++) {
		for ($x=0;$x<$iWidth;$x++) {
			$ord = ord($data[$i]);
			imagesetpixel($im, 
				$x, $y,
				imagecolorallocate($im,
					$ord,
					$ord,
					$ord
				)
			);
			$i++;
		}
	}
	header("Content-Type: image/png");
	imagepng($im);
	imagedestroy($im);
}
?>

作者用了一段PHP脚本来处理这个转换工作。

我们重点关注一下这一段代码:

$ord = ord($data[$i]);
imagesetpixel($im, 
	$x, $y,
	imagecolorallocate($im,
		$ord,
		$ord,
		$ord
	)
);
$ord是文本当前位置的ASCII值,一开始看这一段的时候我十分疑惑,同一个字符的值分别赋值给了(x,y)这个位置的像素的RGB,这不是又增加了文件的大小么。作者是这样解释的:

“I ran into a problem here, since the image is created as a truecolor image and we need it to be 8 bit indexed and PHP won't make an exact conversion. I guess there are ways to create a palletted image from scratch in PHP/GD, but I haven't looked into that yet. The solution for now is to simply run the generated image through something like Photoshop and convert it to 8 bit there.”

我试着用作者的方法先将文本转换成24位的PNG图片,然后再用Photoshop将24位的PNG图片转换为8位的PNG图片,原来247KB的Jquery1.7.2文件压缩到了75KB,压缩比达到3.293。略不及GZIP的压缩能力,但也十分优秀了,关于与GZIP之间的压缩程度差别的原因,会在后文分析介绍。

后来我试着将作者的程序修改,使之具有文件上传以及直接输出8位PNG图像文件的能力。代码将在附录中给出。

$im_d = imagecreate($iWidth, $iHeight);//8位图缓冲区
$im = imagecreatetruecolor($iWidth, $iHeight);//24位图缓冲区
imagecopymergegray($im_d, $im, 0, 0, 0, 0, $iWidth, $iHeight, 100);//拷贝灰度图且100%拷贝
以上三句为我使用的用于24位/8位转换的关键代码,本人对PHP学习尚浅,有更方便快捷的转换方法请批评指出。


然后我们要将图片在客户端解析成文本,这里我遇到了一个问题,就是将图片导入到canvas中进行读取后,读取的数据都为0。经反复改写测试发现,由于要对数据进行读取,首先需要保证图像已经被正确加载,故这里需要用到回调函数以保证图像被正确加载后在开始读取数据。关键部分代码如下:

var image = new Image();
image.src = strFilename;
preImage(strFilename,function(){
	ctx.drawImage(this,0,0,width,height);
	var data = ctx.getImageData(0,0,width,height).data;
	var mystring = [];
	var p = -1
	var length = data.length;
	for (var i = 0; i < length; i+=4) {
		if(data[i] > 0){
			mystring[++p] = String.fromCharCode(data[i]);
		}
	}
	var strData = mystring.join("");
	my_text.value = strData;
});

function preImage(url,callback){
	var img = new Image(); 
	img.src = url;       
	if (img.complete){
		callback.call(img);  
		return;
	}  
	img.onload = function () {  
		callback.call(img); 
	};  
}

4、效果展示

最终成功将文本转化为图片压缩传输,并在本地将图片进行解析:

到这一步只能说明利用这一思路实现的压缩传输在技术上是可行的,但是否实际有效还需对比测试

四、测试分析对比

我们先来看几个文件:


由PHP直接生成的8位PNG图比先生成24位图再经由Photoshop进行8位/24位转换得到的PNG图要稍小一些,通过二进制方式打开文件,我们看到了这两个文件之间的重要差别:

可见Photoshop添加了不少文件信息。

现在我们依旧主要使用jquery1.7.2文件进行测试,测试分为源文件,不同删减等级的文件,高压缩的文件以及一些其他文件:


黄色部分为经过文本删减的文件,可见对于文本这中压缩途径能获得3倍左右的压缩比,同样和文本删减配合使用能达到不错的压缩比。另一点相同和GZIP相同之处是随着文本删减等级的提升,冗余信息减少使得压缩比下降。

绿色部分为经过文本删减和GZIP压缩的文件,可见对于压缩程度很高的文件,再经由这种压缩方式反而增加了文件的大小,原因将在后面介绍。

蓝色部分为随意挑选的一些文件,值得注意的是同样一张LENA图,由于JPEG格式的压缩程度比较高,这一环节的压缩比就很低。

以上测试还可以说明一点,只要有针对性的解析对应文件的算法,这种压缩传输方式也同样适用于原本冗余度较高的文件,这里我只做了图像到文本的解析。

五、总结

首先,利用canvas的相关技术对文件尤其是文本文件进行压缩是可行的,当GZIP压缩由于某些原因受限制不能使用时,这是很好的一种替代的方法,但是其压缩能力只能接近GZIP。经过我的分析,主要原因由以下两点:

原因一:图片文件有其对应的图片文件信息。这些数据对于文本数据来说是无用的,当传输的文件越小,这种影响就越大。以PNG图像为例,PNG由64字节文件头+16字节IHDR块+3的整数倍字节的PLTE块+1字节tRNS块+IDAT块+96字节IEND块组成,其与数据无关的数据至少为180字节

原因二:

图示为PNG压缩的流程,大致和GZIP相同经历了LZ77编码和Huffman编码。这是他们压缩能力接近的一个原因,但是差距在哪里呢。PNG的英文全称为Portable Network Graphics,由于其是为了网络传输而设计的图像文件格式,所以在设计之初就考虑到了网络状况不良的情况,故加入了CRC循环冗余校验码来保证数据不出错,这些校验码就增加了文件的大小,且对有用的文本数据来说是无意义的。

总而言之,作者的这种想法值得肯定,且对于某些环境有独特作用。其优点是在特殊条件下增加了一种压缩传输方式,缺点也是比较明显的,就是要添加额外的解析代码,当有大量文件进行传输的时候,这种方法值得一试,而仅有很少小的文件需要传输,使用这种方式就得不偿失了。

最后,再次感谢并称赞JacobSeidelin的创意想法,并欢迎大家对本文进行批评指正。

六、附录:工程

有需要的也可以直接下载。

http://download.csdn.net/detail/nobeljz/4416642

//index.php
<!DOCTYPE HTML>
<html>
<head>
	<meta charset = "UTF-8"/>
	<title>JS COMPRESS</title>
<body>
	<form enctype = "multipart/form-data" action = "result.php" method = "post">
		<input type = "file" name = "my_file"/>
		<input type = "submit" value = "上传你的文件"/>
	</form>
</body>
</html>

//result.php
<?php
	$file = $_FILES['my_file'];
	$filename = $file['name'];
	$file_upload_po = "./upload_files/".$filename;
	$file_toIMG_po = "./compressed_files/test.png";

	if($file['error']>0){
		echo "<p>文件上传出现错误:".$file[error]."</p>";
	}else{
		move_uploaded_file($file['tmp_name'], $file_upload_po);
		echo "<p>文件上传成功"."</p>";
	}
?>
<!DOCTYPE HTML>
<html>
<head>
	<meta charset = "UTF-8"/>
	<title>JS COMPRESS RESULT</title>
<body>
<?php
	if (file_exists($file_upload_po)) {  
	    $iFileSize = filesize($file_upload_po); 
	    $iWidth = ceil(sqrt($iFileSize / 1));  
	    $iHeight = $iWidth;
	    echo "<p>上传文件大小:\r".$iFileSize."</p>";
	    $im_d = imagecreate($iWidth, $iHeight);
	    $im = imagecreatetruecolor($iWidth, $iHeight);
	    $fs = fopen($file_upload_po, "r");  
	    $data = fread($fs, $iFileSize);  
	    fclose($fs);  
	    $i = 0;  
	    for ($y=0;$y<$iHeight;$y++) {  
	        for ($x=0;$x<$iWidth;$x++) {
	        	if($i < $iFileSize){
		            $ord = ord($data[$i]);
		            imagesetpixel($im,$x,$y,imagecolorallocate($im,$ord,$ord,$ord));  
		            $i++;  
		        }
		        else{
		            imagesetpixel($im,$x,$y,imagecolorallocate($im,0,0,0));  
		            $i++; 
		        }
	        }  
	    }   
	    if(imagecopymergegray($im_d, $im, 0, 0, 0, 0, $iWidth, $iHeight, 100)){
	    	echo "<p>转换成功"."</p>";
		    imagepng($im_d,$file_toIMG_po); 
		    $imgFileSize = filesize($file_toIMG_po);
		    echo "<p>生成图片宽:\t".$iWidth."</p>";
			echo "<p>生成图片高:\t".$iHeight."</p>";
		    echo "<p>转换文件大小:\t".$imgFileSize."</p>"; 
		    echo "<p>压缩比:\t".$iFileSize/$imgFileSize."</p>";
	    	echo "<canvas id = 'canvas' width = '".$iWidth."px' height = '".$iHeight."px'>你的浏览器不支持CANVAS</canvas>";
			echo "<textarea id = 'my_text' value = '' cols = '100' rows = '100'></textarea>";
			echo "<div id = 'picInfo'>由文本转换的png图片</div>";
			echo "<div id = 'textInfo'>由png图片恢复的文本</div>";
	    }
	    imagedestroy($im_d);
	    imagedestroy($im);  
	} 
?>
</body>
	<script>
		var strFilename = <?php echo "\"./compressed_files/test.png\"" ?>;
	</script>
	<link rel = "stylesheet" type = "text/css" href = "css/index.css" />
	<script src = "javascript/imgToText.js"></script>
</html>

//imgToText.js
var canvas = document.getElementById("canvas");  
var my_text = document.getElementById("my_text");
ctx = canvas.getContext("2d");   
var width = canvas.width;
var height = canvas.height;
var image = new Image();
image.src = strFilename;
preImage(strFilename,function(){
	ctx.drawImage(this,0,0,width,height);
	var data = ctx.getImageData(0,0,width,height).data;
	var mystring = [];
	var p = -1
	var length = data.length;
	for (var i = 0; i < length; i+=4) {
		if(data[i] > 0){
			mystring[++p] = String.fromCharCode(data[i]);
		}
	}
	var strData = mystring.join("");
	my_text.value = strData;
});

function preImage(url,callback){
	var img = new Image(); 
	img.src = url;       
	if (img.complete){
		callback.call(img);  
		return;
	}  
	img.onload = function () {  
		callback.call(img); 
	};  
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值