Issuu on Google+

用 HTML5 画布优化图片 原文:http://coding.smashingmagazine.com/2011/08/30/optimize-images-with-html5-canvas/ 译者:踏雪浪人 Blog:http://www.vejia.com 图片经常是站点中最重量级的组件,即使高速因特网成本更加低廉而得到更为广泛的应 用,站点获取较重量级的(组件或者图片)更加迅速。如果你真的关心你的来访者,那么你 (仍需要)花费一些时间来决定是采用高质量但是尺寸较大的(图片)还是质量较低但下载 更快的(图片)。我一直认为现在的浏览器依然没有提高用户计算上图片的能力。在本文, 我将展示一种可行的解决方案。 让我们参照我在最近工作中无意中发现的一副图。正如你所看到的,这幅图是个舞台幕 布,并且(特意)放了一些灯光杂色。

优化这样一幅图将真的花费一些工夫,因为它包含了大量的红色(这将导致在 JPEG 格 式上的更大产出)和杂色(这将导致在 JPEG 格式上可怕的产出并不利于 png 格式的打包)。 我能对这张图最大的优化是 330KB 的 JPEG 图片,这对单张图片来讲是非常巨大的。因此, 我决定用这张图在用户浏览器上进行图片提升的实验。 如果你仔细看这张图片,你将发现它由两个图层组成:杂色和舞台幕布。如果我们移除 杂色,那么图片(可以被)压缩到 70KB 的 JPEG 图片,这非常好。因此,我们的目标就是 要是为用户减少杂色然后纠正在 Web 浏览器中图片的杂色。这将真正的减少下载时间并且 是 Web 页面更好的执行(渲染)。 在 PhotoShop 中,生成单个杂色非常容易:仅仅需要定位到 滤镜 --> 杂色 --> 添加杂 色。但在图片优化中,杂色使得一些像素变暗(IE 没有白色像素) 。这带来了一个新的挑战: 在图片的正片叠底混合模式应用一个杂色层。

HTML5 Canvas (几乎)所有现代的浏览器都支持画布标签。虽然早期的画布只提供了一个画图 API 的实现,现代的实现允许作者解析并操纵每一个像素。这可以用图片数据接口(ImageData


Interface)来完成, (哪个图片数据呢,就是)一个图片的宽,高和一个(存储着)像素点的 数组。 画布像素数组是一个简单的包含每个像素的 RGBA 的数据(译者:RGBA,R 代表红色 G 代表绿色 B 代表蓝色 A 代表透明相关信息)。 (像素)数据数组如下所示:

因此,一个图片数据数组包含了全部像素值*4 元素。举例,一个 200*100 的图片将在 数组中包含 200*100*4 = 80,000 元素。 解析并操作单个画布像素,我们需要从中获取到图片数据,然后更改像素数组然后放置

数据在画布中:

生成杂色 一旦我们知道了如何操作每个画布的像素值,我们将很容易创建杂色层。一个简单的形 成单个杂色的方法像这样:


效果如下所示:

对于初学者来说这相当好,但是我们并不能创建杂色层并在一个图片的场景中用替换 它。当然了,我们需要在正片叠底模式将它混合。

混合模式 每个使用 Abobe Photoshop 或者是其他高级图形编辑器工作的人都知道混合模式:

一些人把图片的混合模式认为是件复杂的事,但是更多的情况是相当简单的表象背后复 杂的算法。例如,有一个正片叠底混合像这样:


那意味着,我们必须有多于两种颜色(每个通道值)并切成 255 片。 让我们改变我们的代码片段:加载一个图片,生成杂色应允许他使用正片叠底混合模式:

效果如下所示:

看上去好多了,但是杂色非常粗糙。我们必须对它使用透明。

透明混合 用透明度处理两个颜色的合成称为“透明混合”。在一个简单的混合实例中,他的算法


是这样的: 这里,alpha 是从 0 到 1 这个区间的透明值构成的。选择哪个颜色为背景色(colorB )和 哪个颜色是覆盖色(colorA )很重要。在这个例子中,背景色将是幕布图片,杂色将是覆 盖色。 让我们增加更多的参数给那个控制透明模式的 addNoise() 方法,并更改主方法适应透 明的图片模式:

这是我们真正想要的效果:在正片叠底混合模式下应用了一个杂色层和 20%的透明在 图片背景之上。


优化 最终的图片看上去非常完美,但是脚本的执行效率却非常差。在我的计算机上,他运行 了大约 300 毫秒。在用户计算机上平均运行时间甚至更长。因此我们必须优化这个脚本。有 一半的代码使用的是浏览器的 API,比如创建画布,或者图片数据并送回,因此我们不能对 它再做更多的事情。其他的另一半主要是应用在舞台上的杂色的循环,它是可以优化的。 我们的测试图片的大小是 1293*897,这个需要 1159821 次循环迭代。一个非常庞大的 数字,因此即便是一个小的修改也可能导致增加重大执行(开销)。 例如,在一个循环中我们计算 1 - alpha 三次,但是这是个静态值。我们需要定义一个 for 循环的外部变量: 然后我们替换所有出现 1 - alpha 的地方为 alpha1 。 下一步,为了杂色像素的生成,我们使用 Math.random() * 255 公式。但是随后的几行, 我们将把这个值分成 255 份,因此 r = pixels[i] * color / 255 。因而我们必须去乘和除。我们 使用一个随机数。在这些优化之后主循环看上去像什么呢:

在这些小的优化之后, addNoise() 方法执行时间在 240 毫秒(20%的改进); 数组两次:一次混合一次透 明合成。但是数组访问是很占用资源的,因此我们需要使用中间变量去存储原始的像素值(IE 下我们每迭代一次都要访问数组一次),想这样: 想起我们是多于百万级迭代,因此必须斤斤计较,我们执行 pixels


执行方法时间下降到 200 毫秒。

极端优化 细心的用户注意到舞台幕布是红色的。换句话说,这个图片数据被只被定义在红色通道。 绿色和蓝色(通道)是空的,因此在算法中没有必要使用他们。

在高等数学中,提到了这个公式:

这个函数的执行时间下降到 100 毫秒,相比较于原始 300 毫秒的(减少)1/3,这非常 令人敬畏。 这个 for 循环包含了简单算法,并且你可以想象我们不能再有更多其他的(优化)。 实际上,我们可以。 在执行循环期间,我们计算随机像素值并将它应用于原始像素值。但是我们并不需要在 每次迭代计算这些随机像素(想想,我们要做多于一百万次)。相当于我们每次计算有限的 随机数并应用他们在原始像素上。这将工作,因为产生值将是。 。。对,随机的,没有特殊情 况下的重复模式--只是随机数据。 关键是选择正确的数组的大小。它应该足够大以至于不会产生图像上可见重复的模式并 且足够小以能够产生在一个合理的速度内。在我的实验中,最好的随机值数组长在图片宽度


的 3.73 倍。 现在,让我们产生一个随机像素的数组然后应用他们在原始的图片上:

这将在 webkit 浏览器中削减 80 毫秒的执行时间,在 Opera 浏览器中改进效果明显。 另外,Opera 会在图片数据数组中包含有浮点数据时执行效率下降,因此我们必须全面 使用 OR 等位运算符进行计算。 最终的代码片段如下:

执行这段使用了 1293*897 图片的代码在我的笔记本上 Safari5.1 用了大约 80 毫秒的时


间,这真的很快。你可以在线上看到结果。


html