如果上传也能压缩

运用 canvas 完成数据压缩

2016/03/15 · HTML5 · 1
评论 ·
Canvas

原稿出处:
EtherDream   

前言

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 个字节:

R = bytes[0] G = bytes[1] B = bytes[2] A = bytes[3]

1
2
3
4
R = bytes[0]
G = bytes[1]
B = bytes[2]
A = bytes[3]

实际上有现存的主意,可批量将数据填充成像素:

img = new ImageData(bytes, w, h); context.putImageData(img, w, h)

1
2
img = new ImageData(bytes, w, h);
context.putImageData(img, w, h)

但是,图片的宽高怎么样设定?

尺寸设定

最简易的,正是用 1px 的万丈。举例有 1000 个像素,则填在 1000 x 1
的图纸里。

但假若有 10000 像素,就不可行了。因为 canvas 的尺码,是有约束的。

不等的浏览器,最大尺寸不平等。有
4096 的,也有 32767 的。。。

以最大 4096 为例,要是老是都用那个幅度,显明不客观。

比方有 n = 4100 个像素,大家接受 4096 x 2 的尺码:

| 1 | 2 | 3 | 4 | … | 4095 | 4096 | | 4097 | 4098 | 4099 | 4100 |
…… 未利用 ……

1
2
| 1    | 2    | 3    | 4    | …  | 4095 | 4096 |
| 4097 | 4098 | 4099 | 4100 | …… 未利用 ……

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

但 4100 = 41 * 100。假诺用这些尺寸,就不会有浪费。

从而,得对 n 分解因数:

n = w * h

1
n = w * h

与此相类似就能够将 n 个像素,刚好填满 w x h 的图纸。

但 n
是质数的话,就无解了。那时候浪费就不可防止了,只是,如何技术浪费最少?

于是乎就产生那样二个主题材料:

怎样用 n + m 个点,拼成二个 w x h 的矩形(0

思索到 MAX 超级小,穷举就能够。

咱俩遍历 h,计算相应的 w = ceil(n / h), 然后搜索最相近 n 的 w * h。

var beg = Math.ceil(n / MAX); var end = Math.ceil(Math.sqrt(n)); var
minSize = 9e9; var bestH = 0, // 最后结出 bestW = 0; for (h = beg; h
end; h++) { var w = Math.ceil(n / h); var size = w * h; if (size
minSize) { minSize = size; bestW = w; bestH = h; } if (size == n) {
break; } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var beg = Math.ceil(n / MAX);
var end = Math.ceil(Math.sqrt(n));
 
var minSize = 9e9;
 
var bestH = 0,          // 最终结果
    bestW = 0;
 
for (h = beg; h  end; h++) {
    var w = Math.ceil(n / h);
    var size = w * h;
 
    if (size  minSize) {
        minSize = size;
        bestW = w;
        bestH = h;
    }
    if (size == n) {
        break;
    }
}

因为 w * h 和 h * w 是同风度翩翩的,所以只需遍历到 sqrt(n) 就足以。

平等,也不须要从 1 开首,从 n / MAX 就可以。

如此那般,大家就能够找到最切合的图片尺寸。

当然,一连的空白像素,最终减掉后会比很小。这一步其实并不非常首要性。

渲染难题

定下尺寸,大家就足以「渲染数据」了。

只是现实中,总有个别意外的坑。canvas 也不例外:

<canvas id=”canvas” width=”100″ heigth=”100″></canvas>
<script> var ctx = canvas.getContext(‘2d’); // 写入的数目 var
bytes = [100, 101, 102, 103]; var buf = new Uint8ClampedArray(bytes);
var img = new ImageData(buf, 1, 1); ctx.putImageData(img, 0, 0); //
读取的数量 img = ctx.getImageData(0, 0, 1, 1); console.log(img.data); //
chrome [99, 102, 102, 103] // firefox [101, 101, 103, 103] // …
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<canvas id="canvas" width="100" heigth="100"></canvas>
<script>
  var ctx = canvas.getContext(‘2d’);
 
  // 写入的数据
  var bytes = [100, 101, 102, 103];
 
  var buf = new Uint8ClampedArray(bytes);
  var img = new ImageData(buf, 1, 1);
  ctx.putImageData(img, 0, 0);
 
  // 读取的数据
  img = ctx.getImageData(0, 0, 1, 1);
  console.log(img.data);
  // chrome  [99,  102, 102, 103]
  // firefox [101, 101, 103, 103]
  // …
</script>

读取的像素,居然和写入的有过错!何况分歧的浏览器,偏差还不等同。

原来,浏览器为了拉长渲染品质,有一个 Premultiplied Alpha
的建制。然而,那会捐躯局地精度!

虽说视觉上并不明显,但用于数据存储,就有标题了。

怎么禁用它?生龙活虎番尝试都没成功。于是,只可以从数量上雕刻了。

借使不采取 Alpha 通道,又会什么?

// 写入的多寡 var bytes = [100, 101, 102, 255]; …
console.log(img.data); // [100, 101, 102, 255]

1
2
3
4
  // 写入的数据
  var bytes = [100, 101, 102, 255];
  …
  console.log(img.data);  // [100, 101, 102, 255]

如此,倒是避开了难点。

因此看来,只可以从数据上伊始,跳过 Alpha 通道:

// pixel 1 new_bytes[0] = bytes[0] // R new_bytes[1] =
bytes[1] // G new_bytes[2] = bytes[2] // B new_bytes[3] = 255
// A // pixel 2 new_bytes[4] = bytes[3] // R new_bytes[5] =
bytes[4] // G new_bytes[6] = bytes[5] // B new_bytes[7] = 255
// A …

1
2
3
4
5
6
7
8
9
10
11
12
13
// pixel 1
new_bytes[0] = bytes[0]     // R
new_bytes[1] = bytes[1]     // G
new_bytes[2] = bytes[2]     // B
new_bytes[3] = 255          // A
 
// pixel 2
new_bytes[4] = bytes[3]     // R
new_bytes[5] = bytes[4]     // G
new_bytes[6] = bytes[5]     // B
new_bytes[7] = 255          // A
 

那时候,就不受 Premultiplied Alpha 的震慑了。

是因为轻便,也足以 1 像素存 1 字节:

// pixel 1 new_bytes[0] = bytes[0] new_bytes[1] = 255
new_bytes[2] = 255 new_bytes[3] = 255 // pixel 2 new_bytes[4] =
bytes[1] new_bytes[5] = 255 new_bytes[6] = 255 new_bytes[7] =
255 …

1
2
3
4
5
6
7
8
9
10
11
12
13
// pixel 1
new_bytes[0] = bytes[0]
new_bytes[1] = 255
new_bytes[2] = 255
new_bytes[3] = 255
 
// pixel 2
new_bytes[4] = bytes[1]
new_bytes[5] = 255
new_bytes[6] = 255
new_bytes[7] = 255
 

如此,整个图片最八只有 256 色。即便能导出成「索引型
PNG」的话,也是能够品味的。

数据编码

聊起底,就是将图像举行导出。

若果 canvas 能直接导出成 blob,那是最好的。因为 blob 可经过 AJAX 上传。

canvas.toBlob(function(blob) { // … }, ‘image/png’)

1
2
3
canvas.toBlob(function(blob) {
    // …
}, ‘image/png’)

唯独,好些个浏览器都不援助。只好导出 data uri 格式:

uri = canvas.toDataURL(‘image/png’) // data:image/png;base64,xxxx

1
uri = canvas.toDataURL(‘image/png’)  // data:image/png;base64,xxxx

但 base64 会增加长度。所以,还得解回二进制:

base64 = uri.substr(uri.indexOf(‘,’) + 1) binary = atob(base64)

1
2
base64 = uri.substr(uri.indexOf(‘,’) + 1)
binary = atob(base64)

那会儿的 binary,就是终极数额了啊?

万豆蔻梢头将 binary 通过 AJAX 提交的话,会发觉其实传输字节,比 binary.length
大。

原来 atob 再次来到的多寡,仍然为字符串型的。传输时,就关系字集编码了。

之所以还需再转变叁次,形成真的的二进制数据:

var len = binary.length var buf = new Uint8Array(len) for (var i = 0; i
len; i++) { buf[i] = binary.charCodeAt(i) }

1
2
3
4
5
6
var len = binary.length
var buf = new Uint8Array(len)
 
for (var i = 0; i  len; i++) {
    buf[i] = binary.charCodeAt(i)
}

那时候的 buf,技巧被 AJAX 维持原状的传导。

最终效果

综合,我们简要演示下:Demo

找一个大块的文书测量试验。比如 qq.com 首页 HTML,有 637,101 字节。

先使用「每像素 1 字节」的编码,各样浏览器生成的 PNG 大小:

Chrome FireFox Safari
体积 289,460 203,276 478,994
比率 45.4% 31.9% 75.2%

个中火狐压缩率最高,减弱了 2/3 的容积。

改动的 PNG 看起来是那样的:

图片 1

而是可惜的是,全数浏览器生成的图形,都不是「256 色索引」的。


再测量试验「每像素 3 字节」,看看会不会有矫正:

Chrome FireFox Safari
体积 297,239 202,785 384,183
比率 46.7% 31.8% 60.3%

Safari 有了成都百货上千的开采进取,可是 Chrome 却更糟了。

FireFox 某个许的进级,压缩率仍然为最高的。

图片 2

大器晚成律缺憾的是,尽管全体图片并未接收 Alpha 通道,但转换的 PNG 仍然为 三拾个人的。

而且,也无从设置压缩品级,使得这种压缩格局,功效并不高。

比较之下 Flash 压缩,差异就大致了:

deflate 压缩 lzma 压缩
体积 133,660 108,015
比率 21.0% 17.0%

同期 Flash 生成的是通用格式,后端解码时,使用典型库就可以。

而 PNG 还得位图解码、像素管理等步骤,很辛劳。

因此,现实中依旧先行利用 Flash,本文只是开脑洞而已。

实际上用场

不过这种艺术,实际依旧平价到过。用在三个不小日志上传的场面(并且无法用
Flash卡塔 尔(英语:State of Qatar)。

因为后端并不解析,仅仅积存而已。所以,可以将日志对应的 PNG
下回地面,在组织者自个儿Computer上解析。

解压更便于,正是将像素还原回数据,这里有个简陋的
Demo。

这么,既缩小了宽带,也节约存款和储蓄空间。

3 赞 4 收藏 1
评论

图片 3

相关文章