利用 canvas 实现数据压缩

710 查看

前言

HTTP 支持 GZip 压缩,可节省不少传输资源。但遗憾的是,只有下载才有,上传并不支持。

如果上传也能压缩,那就完美了。特别适合大量文本提交的场合,比如博客园,就是很好的例子。

虽然标准不支持「上传压缩」,但仍可以自己来实现。

Flash

首选方案当然是 Flash,毕竟它提供了压缩 API。除了 zip 格式,还支持 lzma 这种超级压缩。

因为是原生接口,所以性能极高。而且对应的 swf 文件,也非常小。

JavaScript

Flash 逐渐淘汰,但取而代之的 HTML5,却没有提供压缩 API。只能自己用 JS 实现。

这虽然可行,但运行速度就慢多了,而且相应的 JS 也很大。

如果代码有 50kb,而数据压缩后只小 10kb,那就不值了。除非量大,才有意义。

其他

能否不用 JS,而是利用某些接口,间接实现压缩?

事实上,在 HTML5 刚出现时,就注意到了一个功能:canvas 导出图片。可以生成 jpg、png 等格式。

如果在思考的话,相信你也想到了。没错,就是 png —— 它是无损压缩的。

我们把普通数据当成像素点,画到 canvas 上,然后导出成 png,就是一个特殊的压缩包了~


下面开始探索。。。

数据转换

数据转像素,并不麻烦。1 个像素可以容纳 4 个字节:

事实上有现成的方法,可批量将数据填充成像素:

但是,图片的宽高如何设定?

尺寸设定

最简单的,就是用 1px 的高度。比如有 1000 个像素,则填在 1000 x 1 的图片里。

但如果有 10000 像素,就不可行了。因为 canvas 的尺寸,是有限制的。

不同的浏览器,最大尺寸不一样。有 4096 的,也有 32767 的。。。

以最大 4096 为例,如果每次都用这个宽度,显然不合理。

比如有 n = 4100 个像素,我们使用 4096 x 2 的尺寸:

第二行只用到 4 个,剩下的 4092 个都空着了。

但 4100 = 41 * 100。如果用这个尺寸,就不会有浪费。

所以,得对 n 分解因数:

这样就能将 n 个像素,正好填满 w x h 的图片。

但 n 是质数的话,就无解了。这时浪费就不可避免了,只是,怎样才能浪费最少?

于是就变成这样一个问题:

如何用 n + m 个点,拼成一个 w x h 的矩形(0

考虑到 MAX 不大,穷举就可以。

我们遍历 h,计算相应的 w = ceil(n / h), 然后找出最接近 n 的 w * h。

因为 w * h 和 h * w 是一样的,所以只需遍历到 sqrt(n) 就可以。

同样,也无需从 1 开始,从 n / MAX 即可。

这样,我们就能找到最适合的图片尺寸。

当然,连续的空白像素,最终压缩后会很小。这一步其实并不特别重要。

渲染问题

定下尺寸,我们就可以「渲染数据」了。

然而现实中,总有些意想不到的坑。canvas 也不例外: