【发布时间】:2019-04-14 11:49:21
【问题描述】:
我做了一个 LZW 压缩 TIFF 图像的解码器,所有部件都可以工作,它可以解码各种位深度的大图像,有或没有水平预测,除了一种情况。虽然它可以很好地解码大多数程序(如具有各种编码选项的 Photoshop 和 Krita)编写的文件,但 ImageMagick 的 convert 创建的文件有一些非常奇怪的地方,它产生的 LZW 代码还没有在字典中,我不知道不知道怎么处理。
大多数情况下,LZW 流中尚未在字典中的 9 到 12 位代码是我的解码算法将尝试放入字典中的下一个代码(我不确定应该是一个问题,虽然我的算法在包含此类情况的图像上失败),但有时它甚至可能是未来数百个代码。在一种情况下,明码 (256) 之后的第一个代码是 364,这似乎是不可能的,因为明码清除了我的字典中所有 258 及以上的代码,在另一种情况下,当我的字典只上升到 317 时,代码是 501 !
我不知道如何处理它,但似乎我是唯一一个遇到这个问题的人,其他程序中的解码器可以很好地加载这些图像。那么他们是怎么做到的呢?
这是我的解码算法的核心,显然由于涉及到多少代码,我无法以紧凑的方式提供完整的可编译代码,但由于这是算法逻辑问题,这应该足够了。它严格遵循官方TIFF specification(第61页)中描述的算法,实际上大部分规范的伪代码都在cmets中。
void tiff_lzw_decode(uint8_t *coded, buffer_t *dec)
{
buffer_t word={0}, outstring={0};
size_t coded_pos; // position in bits
int i, new_index, code, maxcode, bpc;
buffer_t *dict={0};
size_t dict_as=0;
bpc = 9; // starts with 9 bits per code, increases later
tiff_lzw_calc_maxcode(bpc, &maxcode);
new_index = 258; // index at which new dict entries begin
coded_pos = 0; // bit position
lzw_dict_init(&dict, &dict_as);
while ((code = get_bits_in_stream(coded, coded_pos, bpc)) != 257) // while ((Code = GetNextCode()) != EoiCode)
{
coded_pos += bpc;
if (code >= new_index)
printf("Out of range code %d (new_index %d)\n", code, new_index);
if (code == 256) // if (Code == ClearCode)
{
lzw_dict_init(&dict, &dict_as); // InitializeTable();
bpc = 9;
tiff_lzw_calc_maxcode(bpc, &maxcode);
new_index = 258;
code = get_bits_in_stream(coded, coded_pos, bpc); // Code = GetNextCode();
coded_pos += bpc;
if (code == 257) // if (Code == EoiCode)
break;
append_buf(dec, &dict[code]); // WriteString(StringFromCode(Code));
clear_buf(&word);
append_buf(&word, &dict[code]); // OldCode = Code;
}
else if (code < 4096)
{
if (dict[code].len) // if (IsInTable(Code))
{
append_buf(dec, &dict[code]); // WriteString(StringFromCode(Code));
lzw_add_to_dict(&dict, &dict_as, new_index, 0, word.buf, word.len, &bpc);
lzw_add_to_dict(&dict, &dict_as, new_index, 1, dict[code].buf, 1, &bpc); // AddStringToTable
new_index++;
tiff_lzw_calc_bpc(new_index, &bpc, &maxcode);
clear_buf(&word);
append_buf(&word, &dict[code]); // OldCode = Code;
}
else
{
clear_buf(&outstring);
append_buf(&outstring, &word);
bufwrite(&outstring, word.buf, 1); // OutString = StringFromCode(OldCode) + FirstChar(StringFromCode(OldCode));
append_buf(dec, &outstring); // WriteString(OutString);
lzw_add_to_dict(&dict, &dict_as, new_index, 0, outstring.buf, outstring.len, &bpc); // AddStringToTable
new_index++;
tiff_lzw_calc_bpc(new_index, &bpc, &maxcode);
clear_buf(&word);
append_buf(&word, &dict[code]); // OldCode = Code;
}
}
}
free_buf(&word);
free_buf(&outstring);
for (i=0; i < dict_as; i++)
free_buf(&dict[i]);
free(dict);
}
至于我的代码在这种情况下产生的结果,从看起来很明显,只有少数代码被严重解码,之前和之后的所有内容都被正确解码,但显然在大多数情况下,一个之后的后续图像这些神秘的未来代码中的一部分由于将其余解码字节移动了几个位置而被破坏了。这意味着我对 9 到 12 位码流的读取是正确的,所以这确实意味着我在 256 字典清除码之后看到了 364 码。
编辑:Here's an example file 包含如此奇怪的代码。我还发现了一个small TIFF LZW loading library,它遇到了同样的问题,it crashes,我的加载程序在该图像中找到了第一个奇怪的代码(当字典只上升到 2051 时,代码为 3073)。好在它是一个小型库,您可以使用以下代码对其进行测试:
#include "loadtiff.h"
#include "loadtiff.c"
void loadtiff_test(char *path)
{
int width, height, format;
floadtiff(fopen(path, "rb"), &width, &height, &format);
}
如果有人坚持深入我的代码(这应该是不必要的,而且它是一个大库)here's where to start。
【问题讨论】:
-
这可能是一个愚蠢的问题,但我想你已经读过 libtiff 是如何做到的?这似乎是正确的位置:gitlab.com/libtiff/libtiff/blob/master/libtiff/tif_lzw.c#L442
-
@cgohlke 我试图在问题中说得很清楚,这不可能是跟踪代码大小的问题。很明显这是一个理论问题,ImageMagick 清楚地认为有可能有这么高的代码,并且大多数解码器似乎都知道如何处理它。
-
@jcupitt 我做到了,尽管我尽量避免试图根据不力求清晰的代码得出结论。但似乎至少在遵循明确代码的情况下,libtiff 会认为高于 255 的以下代码已损坏,这也是我的代码所做的,但这没有帮助。
-
@MichelRouzic 我用我的库(它使用 libtiff 进行 TIFF 加载)尝试了你的示例文件,它对我来说很好。 ImageMagick 还使用 libtiff 进行 TIFF 写入,所以我认为秘密一定在其中。我会在调试器中尝试
convert strange.tif x.png,然后观察LZWDecode(或任何最终被调用的)的执行。 -
会的。事后看来确实应该是显而易见的,但是我想了想,想不出怎么避免读多了(我在想怎么在输入处限制,没想到算输出大小),所以我没有这样做。但同样,规范的伪代码足够明确,以至于您认为它会包含这样的考虑,但是哦,好吧。至少我现在清楚了。
标签: c compression tiff lzw