解码尚未在字典中的 TIFF LZW 代码

Decoding TIFF LZW codes not yet in the dictionary

我做了一个 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 that contains such weird codes. I've also found a small TIFF LZW loading library that suffers from the same problem, 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

伪造的代码来自于尝试解码比我们预期的更多。问题是 LZW strip 有时可能不会以 End-of-Information 257 代码结束,因此当输出一定数量的解码字节时,解码循环必须停止。每个条带的字节数由 TIFF 标记 ROWSPERSTRIP * IMAGEWIDTH * BITSPERSAMPLE / 8 确定,如果 PLANARCONFIG 为 1(这意味着交错通道而不是平面),则将其全部乘以 SAMPLESPERPIXEL。因此,除了在遇到代码 257 时停止解码循环之外,还必须在达到解码字节数后停止循环。