首页->大浪淘沙

DjVu转PDF

作者:马健
邮箱:stronghorse@tom.com
主页:http://www.comicer.com/stronghorse
发布:2009.09.22

一、引言
二、理论
三、实现
    1、JB2->JBig2
    2、IW44->JPEG 2000
    3、多层叠加
    4、彩色文本
四、结论
五、引申
    1、用DjVu技术制作PDF
    2、反向转换
    3、PDF浏览器限制


一、引言

在扫描电子文档领域,PDF与DjVu各有特色,也都各有一些坚定的支持者,所以网上经常能看到求助实现两种格式互相转换的帖子——都希望能转成自己或别人喜欢的格式。网上提供的解决方案也多种多样,从最简单的虚拟打印(PDF与DjVu均有虚拟打印机),到使用专门的工具 (单步)或工具集合(多步)转换都有。

出于兴趣,我最近也在这方面进行了一些技术探索,不过重点不在结果本身(我个人一直不主张在不同格式之间转来转去穷折腾),而在过程:希望能从技术角度比较PDF与DjVu内部数据结构,尽量实现无损转换,同时保持文件长度变化不大。

本文就是上述过程的一个记录。

二、理论

按我个人的理解,DjVu的高压缩比主要来自以下几个方面:

  • 基于MRC(Mixed Raster Content,参见ISO/IEC 16485)模型的分层结构:将扫描图像分解成前景、背景、蒙板层,然后针对不同层的特点,采用最适合的图像压缩算法。在表达文字、图像混合的内容时,这种方法无疑比传统不分层的、眉毛胡子一把抓的静态图像压缩格式(如JPG、PNG、BMP、GIF等)更优秀。不过按照ISO/IEC 16485的建议,如果对图像先分割成子区域(strip)再进行分层,或采用N层结构,可能获得更高的压缩性能。不过DjVu大概认为追求这样的性能提高不太值得,所以一直坚持采用MRC的基本三层 模型。
  • 在分层的基础上,DjVu从阅读心理出发,认为阅读者对扫描页中文字部分的关注度,要高于对图像、底纹的关注度。因此对于文字部分,通常不会对像素尺寸进行缩减,只会在原尺寸上进行编码压缩;而对于图像、底纹部分,一般先进行缩图,然后再编码压缩——通常长、宽缩至原来的1/3~1/12,显示的时候再放大回来。简单算一下就可以知道,就算长、宽只缩至1/3,图像面积也只有原来的1/9大,自然能够大大减小最终的编码长度,付出的代价是:很多DjVu文件的插图看起来模模糊糊的,这其中的原因除了有损压缩外,图像缩放更主要。DjVu文件各层的像素尺寸,可以从DjVuToy导出 的DjVu文件信息中看出,有兴趣的不妨看一看
  • 在编码方面,DjVu的文字层采用JB2压缩算法。这种算法的核心思想是:把整页文字切分成一个个符号(shape),相同的符号不再重复编码,这样整页文字可以用一个无重复的符号集合(称为“字典”,dictionary)、一个页面描述集合来表示。单条页面描述可以用三元组(idx,x,y)表示,idx代表符号在字典中的序号,(x,y)是该符号的显示位置,说白了每条页面描述的意思就是:在(x,y)处显示编号为idx的符号。采用这种方式,不仅页面中的空白部分不再需要编码,而且对于印刷字体(尤其是字母文字), 每一页中符号的重复程度是很可观的,这些重复的符号编码也都可以省略了,所以压缩比要比常规静态图像要大。不过这种算法的问题是:如何判断两个符号是相同的?毕竟图像是扫描出来的,要说两个字一个像素不差不太可能,总要有一个容忍程度,差异超过此容忍程度即认为两个符号不同,否则认为相同。在常规DjVu制作软件中,通常提供给用户三种选择:无损(lossless)、清洁(clean)、有损(lossy),容忍程度从小到大,而最终文件长度则从大到小。DjVu一向标榜的就是高压缩比,所以 常规制作软件的缺省选择都是有损,这样就可能因为把相似字误判为相同字而出现错别字:
    http://djvu.org/forum/phpbb/viewtopic.php?t=659
    http://readfree.net/bbs/read.php?tid=277235
    这个问题是JB2有损压缩的原罪,理论上很难完全避免,实际上各DjVu生成引擎都会在内部进行一些判别以求弥补,但效果如何谁也说不清。所以在有足够的证据(我很怀疑有谁能提出这样的证据)证明某个DjVu引擎不会对中文相似字进行误判之前,我绝对不可能把我自己需要保留的文件压缩成有损JB2。当然,给别人看的就另当别论了。
  • DjVu的插图、底纹部分通常采用IW44压缩算法,这种算法基于小波(Wavelet)分析,原理基础和JPEG 2000差不多,一般采用较高压缩比,代价是图像质量用肉眼就能看出是有损的。

与ISO 32000-1相对照,其实以上特性在PDF中也有:

  • PDF的transparent imaging model支持多层结构,包括透明、半透明,比DjVu的模型结构复杂多了。
  • 从PDF 1.4(对应Acrobat5)开始支持JBig2压缩,这种压缩算法在核心思想上(参见ISO/IEC 32000-1:2008第7.4.7节、ISO/IEC 14492:2001)与DjVu的JB2压缩一模一样。不过JBig2考虑的范围更广泛一些,除文字、线型图外,还考虑到半调(halftone)图像等,因此定义远比JB2复杂。换句话说,JB2数据流可以完全转换成JBig2数据流,但是反向就不一定了——JBig2中的 某些东西在JB2中没有对应。
  • 从PDF 1.5(对应Acrobat6)开始支持JPEG 2000压缩,这种压缩算法的理论基础与DjVu的IW44压缩一样,都是基于小波分析。从实际图像测试结果看,对于同一张连续色调图像,这两种算法在同样的压缩比下,最终的视觉效果差别不明显。换句话说,对于同一张图像,这两种算法压缩出来的文件长度可以差不多,视觉效果也差不多。

所以,在理论上,大多数DjVu可以在转换成PDF时,做到在文件长度变化不大(变化还是有,毕竟文件结构方面存在差异)的情况下,数据无损(JB2->JBig2)或视觉无损(IW44->JPEG 2000)。

注意我说的是“大多数DjVu”,因为如果细看Lizardtech公司2005年版《DjVu Reference v3》,就可以看到其中定义了一种称为“彩色JB2(Colorized JB2)”的东东,我在PDF里没有找到直接对应。不过幸好想制作出这种格式的DjVu也不是一件容易的事情,至少国内主要的DjVu文件来源,包括中美百万、CX等,都没有这种格式的DjVu,所以我上面说是“大多数DjVu”。

另外JB2与JBig2的相似性也不是偶然的,在AT&T的Patrick Haffner、Leon Bottou、Yann Lecun与Lizardtech公司的Luc Vincent合著的论文《A General Segmentation Scheme For DjVu Document Compression》第2章中,对JB2算法的来历进行了介绍:

The mask image is encoded with a new bi-level image compression algorithm called JBZ or DjVuBitonal. It is a variation on AT&T's proposal to the emerging JBIG2 standard. The basic idea of JB2 is locate individual shapes on the page (such as characters), and use a shape clustering algorithm to find similarities between shapes. Shapes that are representative of each cluster (or in a cluster by themselves) are coded as individual bitmaps with a method similar to JBIG1.

看来不仅名字相似,JB2与JBig2追到根子上还有血缘关系,不过似乎JBig2后来又发展出了一些新花样,而JB2就此颓废了——所托非人啊!

三、实现

理论说上一大堆,如果没有一个实际实现,总还是觉得有点虚。所以我就以FreePic2Pdf的PDF生成引擎为基础,加入对DjVu的支持,最终在DjVuToy中实现了DjVu转PDF功能:一次可以转换一本书,包括多级书签,但不支持隐藏文本和注释。

下面分别介绍一下其中几个关键技术的实现原理和方法,及对最终结果的验证。

1、JB2->JBig2

这个部分初看起来似乎没啥悬念:把JB2中的字典、页面描述解码出来,按照JBig2的要求重新编码、封装即可,中间不需要全图转换成位图后重新分割、聚类。

但是实际做过以后才会知道,这中间还是有讲究的:如果不对字典进行处理,直接就编码、封装,最终的结果大概会比最初的JB2数据流长约20%。其中的原因我也是看了Adam Langleyjbig2enc才明白:如果字典中的某些符号在页面描述中多次出现,可以把这些符号单独编成一个字典,那些只出现一次的符号编成另外一个字典,这样可以减小页面描述中的索引位数,最终减小整个数据流长度。这种技术没看到有谁专门命名,姑且称之为“字典二次编码”技术。这种技术对多页共用字典 固然有影响,多单页独享字典也有影响。

除了上述字典二次编码技术外,JBig2的算术编码效率也对最终数据流长度有影响,不过这部分太复杂了,不是一般人能搞定的。

对最终编码结果的验证则很简单:

  • 用DjVuToy可以导出DjVu文件结构,用PdfToy或免费开源的PdfView可以导出PDF文件结构,比较一下其中JB2、JBig2数据流的长度,即可知道编码效率的差异。从实际测试结果看,差异有一些,但是绝对没有网上常见的DjVu宣传资料上宣称的那么大。
  • 用PdfToy或UnicornViewer 0.17+可将PDF中的JBig2数据流转换成JB2并封装成DjVu文件,用DjVuToy可导出转换前后的DjVu文件的字典、页面描述,用FindDupFile可验证这两个文件的字典完全相同,页面描述用Excel重新排序后也可以验证完全相同,因此可认为JB2->JBig2及反向的JBig2->JB2过程均是完全无损的。

2、IW44->JPEG 2000

我本人的数学基础不太好,对小波分析更是望而生畏,所以没有研究是否可能像JB2->JBig2那样,在不解码成位图的情况下实现直接转换,而是采用了一个偷懒的笨办法:先把IW44解码成位图,根据解码前后的数据流长度可以算出压缩比,然后按照这个压缩比,再把位图压缩成JPEG 2000。这里面的关键就是:JPEG 2000压缩允许指定压缩比,保证压缩出来的数据流长度在指定的范围内。

对最终编码结果的验证也很简单:

  • 用DjVuToy导出DjVu文件结构,用PdfToy或PdfView导出PDF文件结构,比较其中BG44/FG44与JPXDecode数据流的长度,即可知道编码效率的差异。从实际测试结果看,差异可以忽略。
  • 用PdfToy或UnicornViewer 0.17+可以将PDF中的JPEG 2000图像无损导出,用图像比较软件可以从统计角度定量比较二者的差异,也可以直接用肉眼比较一下,在我看来都差不多,基本上可以认为是“视觉无损”,除非压缩率超过了一定限度。

如果有谁对小波比较精通,不妨对IW44和JPEG 2000进行一下深入研究,我总觉得这二者是可以直接转换的——研究有成果了别忘记通知我一声。

3、多层叠加

对于DjVu的多层混合显示,在PDF中有不止一种方法可以实现,甚至连DjVu中没有的半透明效果都可以实现。不过最终我选择了用SMask实现,原因很简单:用这种方式在Acrobat中显示时可以指定背景色 ,即成为常说的“透明背景PDF”。

这个的验证没啥好说的,在浏览器里打开看一下就知道了。这个例子是一个三层结构的DjVu文件及转换后的PDF文件,有兴趣的可以比较一下显示效果 。内部数据的比较结果如下:

  • DjVu:蒙板层像素尺寸2774×3543,数据流长度26896字节;前景层232×296(长、宽仅为蒙板层的1/12),数据流长度5138字节;背景层925×1181(长、宽仅为 蒙板层的1/3),数据流长度34334字节。
  • PDF:各层像素尺寸与DjVu一样,数据流长度分别是:27424字节、5083字节、34386字节,差别不大。

各位如果有兴趣,不妨把这个例子DjVu另存为单张静态图像,可以看到文件长度急剧膨胀,对照一下将有助于理解我前面说的DjVu高压缩比的原因

4、彩色文本

“彩色文本”是DjVu的一个独门绝技。如果页面中含有彩色文字,在DjVu中可以有两种实现方法(参见Lizardtech公司2005年出版发行的《Lizardtech DjVu Reference DjVu V3》第7.1.3.1节“Foreground Encoding”):

  • 常规三层法:文字轮廓用JB2压缩,作为蒙板层(Sjbz);颜色部分用IW44压缩,作为前景层(FG44)。上面这个例子就采用这样的技术。为了追求高压缩比,通常对前景层进行大比例缩图(如上面这个例子长宽缩至1/12),这样在还原显示的时候,文字颜色看起来可能会有点怪异,因为 缩放后的前景层总会与原来的有点差异。
  • 彩色文字法:文字轮廓用JB2压缩,成为蒙板层(Sjbz),然后对每个符号的颜色进行编码,成为前景颜色层(FGbz)。

两种方法相比较,后者的编码效率要更高一些,显示时的文字颜色也比较纯正,缺点是每个符号的颜色必须是单一纯色,不能出现变化(如渐变色文字)。而前者的适应范围无疑要更广泛一些,压缩比问题也 通常通过缩图解决,如长宽缩至1/12,则面积仅为原先的1/144,还没开始编码就轻松超过1:100的压缩比。大概正因为如此,所以常见的DjVu都采用了常规三层结构,采用彩色文字的很少见。

以我对PDF的了解,采用彩色文字的DjVu如果想转换成PDF,最无损的办法大概是:把Sjbz数据段拆成“字典”和“页面描述”两个部分,字典中的符号封装成点阵字体嵌入PDF,页面描述中的说明转换成PDF的字符输出指令,FGbz中的颜色描述则转换成PDF的前景色设置指令。显示的时候,按照指定的颜色显示字符,字符点阵来自内嵌字体。

这种方法好是好,但是其中过程的复杂性我只是想一想就失去了尝试的勇气。所以最终还是采用了偷懒的办法:把彩色文字转换成常规三层结构。这种方法实现简单,但是会导致转换出来的PDF文件长度增加许多。好在我自己平时不转,大就大吧。

四、结论

综上所述,大多数DjVu在转换成PDF时,可以在文件长度变化不大的情况下,做到数据无损(JB2->JBig2)或视觉无损(IW44->JPEG 2000),前提是转换的方法和工具得当。

从这一点上说,“DjVu格式的压缩比高于PDF格式”的观点其实是不成立的——在“格式”上PDF也可以实现DjVu的高压缩比,因此二者的差异不在于“格式”,而在于把静态图像转换成最终“格式”的工具和方法。

五、引申

1、用DjVu技术制作PDF

目前常见的PDF制作工具,包括Acrobat,在将静态图像转换成PDF时,多半采用“嵌入”的方式,即将整个静态图像数据流甚至文件嵌入PDF文件中,不进行进一步的处理 (如按MRC模型分层)。这种方法的好处是技术简单、实现方便、图像可以完全无损,缺点是经常有人抱怨这样做出来的PDF文件比DjVu大得多。

而从前面的描述来看,DjVu的高压缩比与它的“分层结构、按需编码”有直接关系,而这是可以复制到PDF中来的。因此我认为如果想提高扫描版PDF的压缩率,可以在PDF制作软件上进行改进:引入商业DjVu制作软件的内核或引擎,对需要转换成PDF的扫描图像进行分层,然后按照分层结果选择最有效的图像压缩算法。即把上面说的“图像->DjVu->PDF”过程简化成“图像->PDF”,中间这一步在PDF制作软件内部悄悄完成了。

当然,如果不嫌麻烦,或者有OCR的技术积累,也可以自己去做分层的开发,但最终结果是一样的。其实在我第一次看到用luratech公司的产品制作出来的高压缩比PDF时,我就怀疑他们是这么干的。这也是促使我去写这篇文章的原因之一。

2、反向转换

在讨论完DjVu转PDF后,一个很自然的问题就是:这样转换出来的PDF,能不能再转回DjVu?

我对这个问题的回答是:看你想怎么转。最简单的办法当然是直接打印到DjVu虚拟打印机上,或者找一个现成的PDF2DjVu软件,喜欢折腾的也可以先把PDF转图片,然后图片转DjVu。

不过既然前面说了半天数据格式转换,那咱们的思维还是别太发散,还是按照同样的思路:能不能从PDF文件数据流里抽取图像数据流,及层次描述,然后尽量无损地转换回DjVu?我的回答是:不一定。理由如下:

  • 对于PDF中的JBig2数据流,如果没有半调图像掺合在里面,则与DjVu的JB2数据流具有对应关系,可以无损转回JB2数据流。不过我在PdfToy和UnicornViewer中实现这个过程的时候,碰到了与最初JB2->JBig2一样的问题:转回来的文件长度要比原DjVu文件长度大。从对djvulibre源代码的分析看,这同样也是因为JB2中的“字典二次编码”造成的,不过我实在没有耐心深入研究,所以采取了一个偷懒的办法:在“导出”界面中增加了一个“二次编码”选项,如果该选项未选中,则用我自己的 偷懒方法,即把JBig2中的数据取出来,直接转换成JB2编码,中间不需要全图解码成位图,这个过程可以验证是无损的;否则把全图解码成位图,然后用minidjvu或djvulibre的cjb2, 按无损参数重新进行分割、聚类,再编码成JB2,这样出来的结果可能造成字典和页面描述的改变,但全图应该是无损的,数据流长度也能变小一点。
  • 对于PDF中的JPEG 2000数据,我也没办法直接转换成IW44,而且由于djvulibre中的IW44压缩接口不支持指定压缩率,所以即使解码成位图后重新压缩,也很难保证文件长度不变。
  • 彩色文字方面,如果不重新处理,我也猜不出该用什么方法才能转回去。

因此,我至今也只实现了把PDF中的JBig2导出为DjVu,但不敢去试PDF->DjVu,而且建议各位也别闲来无事转着玩儿,不然哪天突然后悔了可没地儿买药去。

反向转换的研究虽然进行得不彻底,不过也产生了其他的副产品:在研究过程中,我感觉未来采用JPEG 2000压缩的PDF会增加,因此在UnicornViewer中专门加强了对这方面的支持,并且我名下所有与PDG相关的软件,均开始支持“名为PDG实为JPEG 2000的文件”:如果PDF中的图片实在转不回DjVu,干脆导出成图片看算了。

3、PDF浏览器限制

按照我前面说的方法和工具转换出来的PDF采用了JBig2、JPEG 2000压缩,前者要求Acrobat 5以上版本,后者要求Acrobat 6以上版本的浏览器才能正常显示。好在现在主流的Acrobat版本最低也是7。其他常见的PDF浏览器中,PDF-XChange支持这两种格式没有问题,Foxit需要专门的插件,CajViewer则不支持。我自己的UnicornViewer没有问题,在JPEG 2000方面还进行过专门强化,比Acrobat8的兼容性更好,不过解码速度是个大问题。