尚未在字典中解码TIFF LZW码

时间:2019-04-14 11:49:21

标签: c compression tiff lzw

我制作了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,在这里我的加载程序在该图像中找到第一个奇怪的代码(当字典最多到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

1 个答案:

答案 0 :(得分:1)

伪造的代码来自试图解码超出我们预期的范围。问题在于,LZW条带有时可能不会以信息结尾257代码结尾,因此当输出一定数量的已解码字节时,解码循环必须停止。每条带的字节数由TIFF标签ROWSPERSTRIP * IMAGEWIDTH * BITSPERSAMPLE / 8决定,如果PLANARCONFIG为1(表示与平面相反的交错通道),则将其全部乘以SAMPLESPERPIXEL。因此,除了在遇到代码257时停止解码循环之外,还必须在达到解码字节数之后停止循环。