你好啊,Progressive JPEG,十多年后再相见

来源:互联网 发布:如何在淘宝上买岛国片 编辑:程序博客网 时间:2024/04/19 02:01

本文首发: http://blog.csdn.net/madongchunqiu/article/details/52813924

Web之初,渐进式(Progressive)图片还是很常见的,后来就渐渐消失了。现在都流行整块的高清大图,甚至还要上视频。

本想说说 Progressive JPEG 在移动端(手机)App上的轮回,但其实App兴盛之初,4G也同时上线了,因此也未见其在移动端有多大的应用。只是我们的App有个视频模块,整页都是视频截图,而截图均存放于S3,在国内做测试时速度较慢,逐行扫描(Baseline)出来的图片让整个页面此起彼伏,你可以想象那个场景。老板说在美国效果也没见有多好,于是还是想到了 Progressive JPG。

我们的图片效果还改成过:
1. 图片全部下载后才显示。但这样会有图片一个一个蹦出来的感觉,也很糟糕
2. 在1的基础上,做了一个 crossfade 的动画,图片是渐现式的出来,视觉上缓和很多。效果勉强还可以,就是网速问题会导致刚开始满屏的待下载 placeholder 图片。

之前其实就知道 Pinterest 有个PINRemoteImage,效果做得不错,大家可以鉴赏一番:
PINRemoteImage官方效果图
图一:PINRemoteImage官方效果图

Pinterest这个库利用模糊遮罩,巧妙的将丑陋的 Progressive JPEG 的前期图片装点得清新脱俗。既然效果这么棒,那就上呗。

然而我的文章从这里才开始。

一. 优化:一切都是为了显示效果

既然用上了 Progressive JPEG 了,并且解决了初始图片(以下称为初始 scan)的显示效果问题,那么紧接着的问题来了:到底快了多少?

万能的Github上有个 progressive-scans 的项目,可以将图片用 jpegtran 转成标准 Progressive JPG 后,告诉你每次 scan 占整个图片数据量的百分比。我修改了一点代码,使得可以打印出任意 Progressive JPEG 图片的每次 scan 所占的字节数:scan_jpeg.php 。以下图为例:
原图
图二:图片来自Flickr,并使用 jpegtran 标准渐进式参数编码

用 jpegtran 的标准渐进式参数转码后,得出的每次 scan 的字节数如下:

> jpegtran -optimize -progressive -outfile out.jpg in.jpg

scan No. 1, bytes = 7896 (8.52%)
scan No. 2, bytes = 10884 (11.75%)
scan No. 3, bytes = 4905 (5.29%)
scan No. 4, bytes = 6176 (6.66%)
scan No. 5, bytes = 12560 (13.55%)
scan No. 6, bytes = 15336 (16.55%)
scan No. 7, bytes = 1645 (1.78%)
scan No. 8, bytes = 6717 (7.25%)
scan No. 9, bytes = 7240 (7.81%)
scan No. 10, bytes = 19303 (20.83%)

可以看出初始 scan 仅需下载图片十分之一不到的数据量,效果杠杠的。根据 libjpeg 源代码中所含的 wizard.doc 文件,可以了解其默认的 scan 配置文件是这样的:

# Initial DC scan for Y,Cb,Cr (lowest bit not sent)0,1,2: 0-0,   0, 1 ;# First AC scan: send first 5 Y AC coefficients, minus 2 lowest bits:0:     1-5,   0, 2 ;# Send all Cr,Cb AC coefficients, minus lowest bit:# (chroma data is usually too small to be worth subdividing further;#  but note we send Cr first since eye is least sensitive to Cb)2:     1-63,  0, 1 ;1:     1-63,  0, 1 ;# Send remaining Y AC coefficients, minus 2 lowest bits:0:     6-63,  0, 2 ;# Send next-to-lowest bit of all Y AC coefficients:0:     1-63,  2, 1 ;# At this point we've sent all but the lowest bit of all coefficients.# Send lowest bit of DC coefficients0,1,2: 0-0,   1, 0 ;# Send lowest bit of AC coefficients2:     1-63,  1, 0 ;1:     1-63,  1, 0 ;# Y AC lowest bit scan is last; it's usually the largest scan0:     1-63,  1, 0 ;

文档内附有一个能解释95+%的说明,大意是说上面的文件中,每一行(entry) 代表着一次 scan 的描述,这个描述以冒号(:)分开,左边代表颜色的选取(根据JPEG 文件内的配置这里会不同,例如:0-Y, 1-Cb, 2-Cr),右边代表选择 DCT 变换后选用哪些 bits。右边四个数Ss、Se、Ah、Al,前两个指定位置范围,后两个指定选用位置的哪些 bit。wizard.doc 文件关于这四个参数的描述有点不太清楚,我在 books.google.com 上搜到了更详尽的描述:

Ss: Start of spectral selection OR predictor selection, …
Se: End of spectral selection, …
Ah: Successive approximation bit position high, …
Al: Successive approximation bit position low OR point transform, …

按照这些描述,可以将 jpegtran 默认的 scan 顺序绘制如下:

下面三张图中,纵向是颜色成份,横向是 8x8 DCT 后按权重排序(zigzag) 的位置信息。圈圈中的数字是扫描次数 (scan number),该配置总共有10次scan。

jpeg_scan1
图三-1:前5次 scan 将所有位置上的高位(红色)信息发送出去

jpeg_scan2
图三-2:第6次 scan 将Y信息的中位(绿色)信息发送出去

jpeg_scan3
图三-3:最后4次 scan 将所有位置上的最低位(蓝色)信息发送出去

二. 分析:死理性的不归路

jpegtran 的默认配置效果还不错,不过我们当然不想止步于此,咱还想着 “私人定制”呢。

为了在第一时间获取到数据,初始 scan 的数据量当然是越小越好,小到最少可以是?

# scanfile1# 将DC位置的信息分成16份发送,Al(最后一个数字)是4bit0,1,2: 0-0,   0, 15 ;# 下略

运行:

> jpegtran -optimize -scans scanfile1 -outfile out.jpg in.jpg

现实的恶意凸显了,jpegtran 报错,不支持 scanfile1 的配置。


继续尝试,直到将Al设置成10,jpegtran 才接受了。也就是说将 DC 位置的信息做11次发送:

# scanfile2# 将DC位置的信息分成11份发送0,1,2: 0-0,   0, 10 ;# 下略

使用上面的程序看看初始 scan 的数据量,不错哟:

scan No. 1, bytes = 2004 (1.97%)

这里有个程序:jpeg_split 可以将每次 scan 的数据分离出来,可以看到每次 scan 后的图片。那么我们最小数据量的图长啥样呢?这里…
scanfile2图
图四:将 DC 分成11份来传对应的初始 scan 图

不忍直视!看来数据太少了的确也有问题。


继续加大输出:

# scanfile3# 将DC位置的信息分成7份发送0,1,2: 0-0,   0, 6 ;# 下略

数据量:

scan No. 1, bytes = 2584 (2.70%)

scanfile3图
图五:将 DC 分成7份来传对应的初始 scan 图

感觉好了一些,但还是不能看啊。


再一次加大输出:

# scanfile4# 将DC位置的信息分成4份发送0,1,2: 0-0,   0, 3 ;# 下略

数据量:

scan No. 1, bytes = 5014 (5.39%)

scanfile4图
图六:将 DC 分成4份来传对应的初始 scan 图

挺不错的。相对于标准图(图二),第一次的传输量下降了一半,效果还是让人满意的。


还有一个想法是初始 scan 仅传Y,不传Cb、Cr,这样

# scanfile5# 初始scan仅传Y,不传Cb、Cr0: 0-0,   0, 3 ;1,2: 0-0, 0, 3 ;# 下略

按照 scanfile5,第一次传输的数据差不多又要节省一半还多,但却是一张灰度图,我是无法接受的。

写到这里,基本能玩的都玩到了,结论是:jpegtran 的默认配置基本够用了,如果对初始 scan 的数据要求更加严格,可以采用 scanfile4,但可能会面临初始 scan 图像偏色或者严重失真的情况

附: 其它方向

在网上查资料时,有个人提出了自己的需求:初始 scan 希望能更清晰一些,这样既能加快显示,又能在一开始就呈现出较好的效果。
然而,Progressive JPEG 的标准在这里设置了一道坎。从 book.google.com 中搜索到的 JPEG 标准中写到:

Each band is a continuous range of DCT coefficients using the zigzag order. The only restriction on the coefficients in a band, other than that the band must contain a continuous range, is that the DC component must be in a band by itself. At a minimum, a component will be divided into two scans: one containing the DC coefficient and the other the AC coefficients. At the most extreme the component can be divided into 64 bands with one coefficient in each.

意思就是说,DC 只能单独发送,因此初始 scan 理论上最大的发送配置是:

# scanfile6# 初始scan发送尽可能多的数据0,1,2: 0-0,   0, 0 ;# 下略

数据量:

scan No. 1, bytes = 9490 (10.09%)

scanfile6图
图七:最大限度发送的初始 scan 图

这张图和标准图相比,传输的数据量和呈现效果都没有较大区别。所以,这个”第一次就传送较大的数据量以呈现更好的效果”的尝试,只能宣告失败了。


下表总结了上面的各初始 scan 图像质量和对应数据量:

初始scan 数据量百分比 图像质量 DC分成11份 1.97% 惨不忍睹 DC分成7份 2.70% 较大偏差 DC分成4份 5.39% 可以接受 DC分成2份(标准) 8.52% 可以接受 DC最大程度发送 10.09% 与标准无甚不同

(完结)

0 0