我制作了LZW压缩TIFF图像的解码器,所有部件都可以工作,除了一种情况外,它可以在有或没有水平预测的情况下解码各种位深度的大图像。虽然它解码大多数程序(如Photoshop和Krita编写的各种编码选项)编写的文件很好,但是对于ImageMagick的convert
创建的文件有一些非常奇怪的东西,它产生的LZW代码还没有出现在字典中,我不知道知道如何处理它。
大多数时候LZW流中尚未出现在字典中的9到12位代码是我的解码算法将尝试放入字典的下一个代码(虽然我不确定它应该是一个问题,尽管我的算法在包含此类情况的图像上失败了,但有时它甚至可能是未来的数百个代码。在一种情况下,清除代码(256)之后的第一个代码是364,这似乎是不可能的,因为清除代码清除所有代码258及以上的字典,在另一种情况下,当我的字典仅达到317时代码为501 !
我不知道如何处理它,但似乎我是唯一一个有这个问题的人,其他程序中的解码器加载这样的图像很好。那他们怎么做呢?
这是我的解码算法的核心,显然是由于涉及多少代码我无法以紧凑的方式提供完整的可编译代码,但由于这是算法逻辑的问题,这应该足够了。它紧跟官方TIFF specification(第61页)中描述的算法,实际上大多数规范的伪代码都在评论中。
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,我的加载器在这个图像中找到了第一个奇怪的代码(代码3073,当字典只上升到2051)。好处是,因为它是一个小型库,您可以使用以下代码进行测试:
#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。
伪造的代码来自试图解码超过我们应有的。问题在于LZW条带有时可能不以信息结束257代码结束,因此解码循环必须在输出一定数量的解码字节时停止。每个条带的字节数由TIFF标签ROWSPERSTRIP * IMAGEWIDTH * BITSPERSAMPLE / 8决定,如果PLANARCONFIG为1(这意味着交错通道而不是平面),则将它全部乘以SAMPLESPERPIXEL。因此,在遇到代码257时停止解码循环之后,还必须在达到解码字节计数之后停止循环。