当长度未知时,是否可以使用 libtiff 解码 CCITT 编码的数据?

Is it possible to use libtiff to decode CCITT-encoded data when the length is not known?

在这个问题的答案中:c++ decode CCITT encoded images in pdfs

指出libtiff可以用来解码CCITT编码的图像。当然,我们必须在前面加上一个 TIFF 头才能使 CCITT 流成为一个有效的 TIFF 文件。

但是,PDF 文件中的某些图像是 内联图像,虽然给出了宽度、高度和位深度,但没有给出它们的长度。读取 PDF 的程序需要对 CCITT 流进行解码,读取解码数据的 (width * height * depth) 位,并且在读取数据后无论它在哪里,这就是内联图像的结尾。然后它应该继续下一个页面标记命令,依此类推。

这是一个问题。 TIFF 图像文件目录必须指定图像数据的每个条带中有多少字节,但在解码之前我们不知道编码数据中有多少字节实际上属于图像,但我们不能不使用 libtiff 解码图像...

这里有使用 libtiff 的方法还是我们需要自定义 CCITT 过滤器代码?

严格来说(是否可以使用 libtiff...?),可以。它涉及一些黑客攻击,但不会太多。

事实:数据将由一个条带组成,因为没有任何偏移信息,所以我们唯一的偏移量是零。我们只需要阅读条带即可。

事实:该数据是W*H 1位深像素矩阵的压缩。

第一步:估计压缩流的最大可能长度。这大约是 W*H 的 15%,即 W=1000 和 H=1000 时你得到 150000 字节。该值将始终比实际值多 。如果我们由于找到了正确的 EI end-image 标签而得到更好的估计,那就更好了,但不是必需的。

第 2 步:构建一个 "virtual" TIF 文件。这将由 49 49 2a 00 AA BB CC DD 形式的 header 组成,其中 0xDDCCBBAA 是估计长度加 8;其次是我们估计的数据流;后跟一个 TIFF 目录。

第 3 步:TIFF 目录将始终具有相同的结构;其中的一些值是偏移量,并且与 IFD 位置 0xDDCCBBAA 无关。引用自 TIFF6 规范(注意字节顺序是颠倒的——Motorola,不是 Intel endian):

TIFF 6.0 Specification Final—June 3, 1992                         20

Putting it all together (along with a couple of less-important fields that are discussed
later), a sample bilevel image file might contain the following fields

A Sample Bilevel TIFF File

Offset Description Value
(hex) (numeric values are expressed in hexadecimal notation)
Header:
0000 Byte Order     4D4D 
0002 42             002A
0004 1st IFD offset 00000014
IFD:
0014 Number of Directory Entries 000C
0016 NewSubfileType              00FE 0004 00000001 00000000
0022 ImageWidth                  0100 0004 00000001 000007D0
002E ImageLength                 0101 0004 00000001 00000BB8
003A Compression                 0103 0003 00000001 8005 0000
0046 PhotometricInterpretation   0106 0003 00000001 0001 0000
0052 StripOffsets                0111 0004 000000BC 000000B6(*1)
005E RowsPerStrip                0116 0004 00000001 00000010
006A StripByteCounts             0117 0003 000000BC 000003A6(*2)
0076 XResolution                 011A 0005 00000001 00000696(*3)
0082 YResolution                 011B 0005 00000001 0000069E(*4)
008E Software                    0131 0002 0000000E 000006A6(*5)
009A DateTime                    0132 0002 00000014 000006B6(*6)
00A6 Next IFD offset             00000000
Values longer than 4 bytes:
(*1) StripOffsets Offset0        00000008
(*2) StripByteCounts Count0
(*3) XResolution 0000012C 00000001
(*4) YResolution 0000012C 00000001
(*5) Software “PageMaker 4.0”
(*6) DateTime “1988:02:18 13:59:59”

在上面,0xDDCCBBAA 实际上是 0014,所有其他偏移量都在后面。

我使用使用 ImageMagick 生成的 single-strip TIFFG4 图像并 tiffcp 编辑为 1-strip CCITT 格式进行了一些测试。 header 略有不同(我没有看到规范规定应该存在的软件和日期时间标签)。否则它会检查。

我们现在有一张损坏的 TIFF 图像,其中有一条超长条带,它在内存中。

使用TIFFClientOpen,我们可以access it as if it was a disk image

尝试读取第一个条带现在将导致错误并且程序中止:

TIFFFillStrip: Read error on strip 0; got 143151 bytes, expected 762826.

通过使用 TIFFSetErrorHandlerTIFFSetErrorHandlerExt 我们设置自己拦截这个错误,并解析它,从而恢复 143151 信息,而不是中止。

我们需要向 TIFFClientOpen 提供回调,但它们都很简单:

TIFFReadWriteProc readproc(h, *ptr, n) // copy n bytes from FakeBuffer+pos into ptr, update pos to pos + n, ignore h.
TIFFReadWriteProc writeproc            // Throw an error. We don't write
TIFFSeekProc seekproc                  // update pos appropriately
TIFFCloseProc closeproc                // do nothing
TIFFSizeProc sizeproc                  // return total buffer size
TIFFMapFileProc mapproc                // Set to NULL
TIFFUnmapFileProc unmapproc            // Set to NULL

处理起来确实比较啰嗦,但是至于可行性, 可以完成。

我用 C 语言进行了 运行 测试,从我在网上找到的 inline-image BI/ID/EI PDF 中手动提取 CCITT 流,并按上述方法阅读。

如果我有 sure-fire 识别正确 EI 的方法 - 我已经挖掘了 a message by Tilman Hausherr 解释了一个 hack 来识别跟随 EI 的有效 PDF 运算符以便这样做,这使得我认为可能没有太多更好的方法 - 我总是可以估计正确的偏移量,并直接从 PDF 生成正确且可读的 TIFF 文件,甚至根本不涉及 libtiff。