Alexander Abakumov
Alexander Abakumov

Reputation: 14539

BitMiracle LibTIFF.NET Can't Decompress TIFF Previously Created By Itself

I've implemented a class that reads 24 bit-per-pixel TIFF generated by Microsoft.Reporting.WinForms.ReportViewer, converts it to a 1 bit-per-pixel TIFF and stores the result into a file.

This part is working just fine - I'm able to open the resulting TIFF in a TIFF viewer and view the contents.

For compression I'm using the following codec:

    outImage.SetField(TiffTag.COMPRESSION, Compression.CCITT_T6);

Now I'm trying to read the same 1 bit-per-pixel TIFF and decompress it. I wrote the following methods:

public static void DecompressTiff(byte[] inputTiffBytes)
{
    using (var tiffStream = new MemoryStream(inputTiffBytes))
    using (var inImage = Tiff.ClientOpen("in-memory", "r", tiffStream, new TiffStream()))
    {
        if (inImage == null)
            return null;

        int totalPages = inImage.NumberOfDirectories();
        for (var i = 0; i < totalPages; )
        {
            if (!inImage.SetDirectory((short) i))
                return null;

            var decompressedTiff = DecompressTiff(inImage);
...
}

private static byte[] DecompressTiff(Tiff image)
{
    // Read in the possibly multiple strips 
    var stripSize = image.StripSize();
    var stripMax = image.NumberOfStrips();
    var imageOffset = 0;
    int row = 0;

    var bufferSize = image.NumberOfStrips() * stripSize;
    var buffer = new byte[bufferSize];

    int height = 0;
    var result = image.GetField(TiffTag.IMAGELENGTH);
    if (result != null)
        height = result[0].ToInt();

    int rowsperstrip = 0;
    result = image.GetField(TiffTag.ROWSPERSTRIP);
    if (result != null)
        rowsperstrip = result[0].ToInt();
    if (rowsperstrip > height && rowsperstrip != -1)
        rowsperstrip = height;

    for (var stripCount = 0; stripCount < stripMax; stripCount++)
    {
        int countToRead = (row + rowsperstrip > height) ? image.VStripSize(height - row) : stripSize;
        var readBytesCount = image.ReadEncodedStrip(stripCount, buffer, imageOffset, countToRead); // Returns -1 for the last strip of the very first page
        if (readBytesCount == -1)
            return null;

        imageOffset += readBytesCount;
        row += rowsperstrip;
    }

    return buffer;
}

The problem is that when ReadEncodedStrip() is called for the last strip of the very first page - it returns -1, indicating that there is an error. And I can't figure out what's wrong even after debugging LibTIFF.NET decoder code. It's something with EOL TIFF marker discovered where it's not expected.

By some reason, LibTIFF.NET can't read a TIFF produced by itself or most likely I'm missing something. Here is the problem TIFF.

Could anyone please help to find the root cause?

Upvotes: 1

Views: 1072

Answers (1)

Alexander Abakumov
Alexander Abakumov

Reputation: 14539

After a more than a half day investigation, I've finally managed to detect the cause of this strange issue.

To convert from 24 bit-per-pixel TIFF to 1 bit-per-pixel, I ported algorithms from C to C# of the the 2 tools shipping with original libtiff: tiff2bw and tiffdither.

tiffdither has the bug that it doesn't include last image row in the output image, i.e. if you feed to it an image with 2200 rows height, you get the image with 2199 rows height as output.

I've noticed this bug in the very beginning of the porting and tried to fix, but, as it turned out eventually, not completely and the ported algorithm actually didn't write the last row via WriteScanline() method to the output TIFF. So this was the reason why LibTIFF.NET wasn't able to read last strip\row of the image depending on what reading method I used.

What was surprising to me is that LibTIFF.NET allows to write such actually corrupted TIFF without any error during writing. For example WriteDirectory() method returns true in this situation when image height set via TiffTag.IMAGELENGTH differs from the actual coount of rows written to it. However, later it can't read such the image and the error is thrown while reading.

Maybe this behavior inherited from the original libtiff, though.

Upvotes: 1

Related Questions