扫描的合同是白纸黑字的,每英寸有几百个点。作为每像素 1 位的位图存储时它已经很小了,然而一百个这样的页面仍然会让 PDF 膨胀到无法通过电子邮件发送的大小。正确的滤镜能改变这种计算。JBIG2 是 ISO 32000-1 为双色调图像定义的压缩率最高的格式,在成堆的扫描文本上,它通常能将 CCITT Group 4 产生的文件大小减半。当输入内容是通过传真、扫描或其他方式减少为两种颜色时,这是首选的滤镜,而且 HotPDF 可以直接将其写入 PDF 中
该格式之所以能获得如此高的压缩率,是因为它采用了通用图像编解码器所不具备的两个理念。它对黑色连续像素(runs)如何放置在白色背景上进行建模,并且它注意到扫描页面主要由相同的几百个字形重复数千次组成。理解这两点可以让你有针对性地选择编码选项,而不是靠猜测
JBIG2 在 PDF 规范中的位置
ISO 32000-1 在第 7.4.7 节中将 JBIG2Decode 列入流滤镜中,从 PDF 1.4 开始提供。它仅适用于一个地方:/BitsPerComponent 为 1 且其色彩空间解析为单个通道的图像 XObject。这就是重点。JBIG2 是一种双色调编解码器,因此它在处理照片时从不与 DCT 或 JPXDecode 竞争。它在文档扫描仪产生的这种双色调页面上与 CCITTFaxDecode(Group 3 和 Group 4 传真滤镜)竞争
解码器消耗嵌入的 JBIG2 组织结构,标准称之为 PDF 配置文件,其中每个图像流保存的是一系列片段,而不是单纯的比特流。可选的 /JBIG2Globals 流包含在同一文档中多个图像之间共享的片段,这种机制允许重复内容在整个文件中只存储一次,而不是每页存储一次。HotPDF 默认发出每图像流,并保持全局通道空闲,除非后端要求使用它
后端优先的编码器架构
一个完整的 JBIG2 编码器是一个庞大的软件,而其中最激进的部分在历史上一直受到专利的阻碍,并且在不适合所有产品的许可下发布。HotPDF 通过将接口与引擎分离来解决这种张力。单元 HPDFJBIG2 定义了库的其余部分所做的调用,并附带了一个适度的内置编码器,以便 JBIG2 开箱即用。当需要生产级压缩率时,你注册一个更强大的引擎,库就会委托给它,而无需更改你的调用代码
该切换是单个注册调用。如果没有注册后端,编码器将退回到其内置路径。注册一个后,后续的每次编码都会通过它运行
uses
HPDFJBIG2;
// Query what is active, then optionally install a stronger engine.
if not IsJBIG2EncoderBackendAvailable then
// Production backend not present: HotPDF uses its built-in MMR path.
RegisterJBIG2EncoderBackend(MyVendorJBIG2Encode);
// Later, to return to the built-in behaviour:
// ClearJBIG2Backends;
解码也存在相同的挂钩机制,即 RegisterJBIG2DecoderBackend,可以通过 IsJBIG2DecoderBackendAvailable 探测它。这就是为什么库附带了一个小型内置路径加上一个后端接口,而不是一个单一的编码器。内置路径使二进制文件保持精简,没有许可纠纷,而接口则允许获得了完整编码器许可的团队插入它,而完全不需要接触 PDF 写入层
编码选项实际权衡了什么
编码通过 TJBIG2EncodeOptions 配置,这是一个包含 Lossless、UseGlobalSegments、UseSymbolDictionary 和 LossyLevel 字段的记录。对组件友好的包装器 THPDFJBIG2Options 发布了 Lossless、UseSymbolDictionary 和 LossyLevel,因此可以从对象检查器(Object Inspector)设置它们,它在内部转换为该记录。有三个意图驱动着这些设置
无损重构保留每一个像素。将 Lossless 设为 True 且 LossyLevel 保持为 0,解码后的位图将与输入内容逐位完全相同。对于线条图、工程图以及任何丢失像素可能改变含义的页面(如签名或图章),这是唯一安全的选择。符号字典编码打开了文本感知的去重功能,这也是区分 JBIG2 与传真滤镜的选项。有损级别(0 到 9 之间的整数)允许强大的后端通过将近乎相同的标记视为相同的符号,在保真度和大小之间进行权衡。零表示无损。内置编码器仅遵循无损路径并忽略任何非零的有损级别,因此更高的级别只有在注册了实现它们的后端后才生效
var
Options: TJBIG2EncodeOptions;
begin
Options := DefaultJBIG2EncodeOptions; // Lossless True, symbol dictionary on
Options.Lossless := True;
Options.LossyLevel := 0; // 0 keeps every pixel
Options.UseSymbolDictionary := True; // dedupe repeated glyphs
// Pass Options to a backend, or let THPDFJBIG2Options carry them.
end;
符号字典以及为什么文本扫描能胜出
扫描的文本页面并不是真正意义上的单词图像。它是相同的字母 e 打印了几百次,相同的 t,相同的逗号,每个实例都是一个底层形状的轻微噪点副本。符号字典捕获了这种结构。编码器将页面上截然不同的标记收集到字典中,存储每个形状一次,然后将页面记录为引用字典条目的位置列表。同一字形出现一千次,其成本为一个存储的位图加上一千次低成本的放置操作
这正是 JBIG2 领先于 CCITT Group 4 的地方。Group 4 针对其上方的一行对每条扫描线进行编码,没有字形的概念,因此每次字母出现时它都要付出每个字母的全部成本。JBIG2 只需要付出一次成本。当相同的字典提升为文档级全局流时,节省量会在多页扫描中成倍增加,因为一页接一页共享的形状在整个文件中只需存储一次。在密集的文本上,差异不是边缘性的,这正是 JBIG2 存在的理由
用于其他一切的通用区域和 MMR
并非每个双色调图像都是文本。地图、示意图、工程图和混合页面都有任何字典都无法总结的线条图。对于这些情况,JBIG2 对通用区域(直接压缩的像素矩形,没有任何符号训练)进行编码。该标准允许通用区域使用 MMR,即 Group 4 传真已经使用的修改版修改版 READ(modified modified READ)编码,它针对每行像素上方的一行像素进行建模
这是 HotPDF 附带于其内置编码器中的路径。当没有注册后端且请求为无损时,库将位图作为单个 MMR 通用区域压缩,并将其包裹在 PDF 配置文件所需的 JBIG2 片段结构中。它不需要字典,不需要训练遍,也不需要第二个引用的图像,因此它是线条图和混合双色调内容的可靠默认选择。在纯文本上,它无法匹配完整的符号字典编码器,但它始终正确、始终无损并且始终存在。它的编码器表面只有一个调用
var
Encoder: THPDFJBIG2Encoder;
ImageData: TJBIG2ByteArray;
Scanlines: TJBIG2ScanlineArray; // one byte array per row, MSB-first
W, H: Integer;
begin
// Scanlines, W and H describe a 1-bit page; each row is (W + 7) div 8 bytes.
Encoder := THPDFJBIG2Encoder.Create;
try
if Encoder.EncodeToByteArray(Scanlines, W, H, ImageData) then
// ImageData now holds a JBIG2 stream ready for a /JBIG2Decode XObject.
;
finally
Encoder.Free;
end;
end;
在构建文档时开启它
在日常使用中,你不需要直接接触编码器类。HotPDF 将 JBIG2 作为文档的图像压缩选项公开。枚举 THPDFImageCompressionType 包括 icJBIG2,旁边还有 Flate、JPEG 和 CCITT 选项,文档带有一个 JBIG2Options 属性,类型为 THPDFJBIG2Options,用于保存在选择该压缩时使用的设置。在你添加想要以此方式压缩的双色调图像之前配置这两者
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.ImageCompressionType := icJBIG2; // route 1-bit images through JBIG2
Pdf.JBIG2Options.Lossless := True; // keep every pixel
Pdf.JBIG2Options.UseSymbolDictionary := True;
Pdf.JBIG2Options.LossyLevel := 0;
// Add pages and place your scanned 1-bit images here.
finally
Pdf.Free;
end;
end;
值得注意的一个便利功能是 DBGridHotPDFExport 附加组件,它将 TDBGrid 直接渲染为 PDF。其输出主要为双色调的线条和文本,因此为 JBIG2 配置的文档可使这些导出内容保持紧凑,而无需你进行任何额外处理。本博客中两个相关主题深入探讨了周边的流程。关于在构建报告时如何放置图像和字体,请参阅 Delphi 中带有字体和图像的报告输出。当压缩文档必须满足存档配置文件时,Delphi 中的 PDF/A、PDF/X 和 PDF/UA 验证中的规则告诉你特定的合规级别接受哪些滤镜。JBIG2 是适用于 Delphi 和 C++Builder 的 HotPDF Component 的一部分,与此处其他地方介绍的加载、编辑和加密 API 并列提供