2017.04.03 17:05 "[Tiff] Reading images with bits per pixel not a multiple of 8", by Dinesh Iyer

2017.04.03 17:05 "[Tiff] Reading images with bits per pixel not a multiple of 8", by Dinesh Iyer

Hi everyone,

I am trying to use libtiff to read TIFF files and I am looking for clarification when it comes to the layout of images which have bits per pixel that are non-standard such as 2, 4, 11 etc.

My understanding is that after calling TIFFReadEncodedStrip/Tile the data I get back is packed i.e. one sample might have all its bits stored across a byte-boundary. Is this true for all cases or does this apply only for PackBits compression?

I was able to read 4-bit files using my code but when I tried to extend this for a 11-bit file (yes, we have one in our test-suite), I got a garbled image.

The image dimensions are:

height = 154, width = 287, RowsPerStrip = 20, bitsPerSample = 11, SamplesPerPixel = 1.

So, the number of bytes in 1 row of a strip will be ceil(11*287/8) = 395. Hence the number of bytes per strip will be 395*20 = 7900. This value, 7900, matches what TIFFStripSize returned.

My understanding of the layout is as below:

Byte1, Byte2, Byte3, Byte4, Byte5, Byte6,...

1st sample = Entire Byte1, Bit7,Bit6, Bit5 (of Byte2)
2nd sample: Bits4-0 (of Byte2), Bits7-2 (of Byte3)
...

Is my understanding of the layout correct?

The code I am using is below:

// I am using the term slice in my code to mean either strip/tile as appropriate.

uint32_T destSliceWidth = 287;
uint32_T destSliceHeight = 20;
uint16_T samplesPerPixel = 1;
uint16_T bitsPerSample = 11;

// Create a buffer to hold the appropriate number of bytes. auto dataBuffer = createBuffer<uint16_T>(destSliceWidth*destSliceLength*samplesPerPixel);

// readRawSlice calls TIFFReadEncodedStrip(). std::unique_ptr<uint8_T[]> dataSlice = readRawSlice();

// Read 2-bytes at a time. The readUnitSize logic below should handle this correctly.

uint16_T* srcPtr = reinterpret_cast<uint16_T*>( dataSlice.get() );

uint16_T* destPtr = allocatedOutput<uint16_T>(destSliceWidth, destSliceHeight, samplesPerPixel);

// This will evaluate to 395
size_t srcNumBytesPerRow = static_cast<size_t>(
                                std::ceil(
_imageInfo.sliceWidth*samplesPerPixel*bitsPerSample / 8.0 ) );

// Number of samples (not bits or bytes) in this strip size_t destTotalNumberOfSamples = destSliceLength*destSliceWidth*samplesPerPixel;

// Each read from the dataSlice will read 16-bits

uint16_T readUnitSize = sizeof(uint16_T)*8;    // In terms of bits

// Offset into the read-unit
uint16_T readUnitOffset = readUnitSize; // In terms of bits

// The actual computed 11-bit value of the sample
uint16_T srcSampleVal = 0;

for( size_t destCnt = 0; destCnt < destTotalNumberOfSamples; destCnt++ )
{
    // If end-of-row reached, then move 395*rowNum byes from the start of
dataSlice to get to the next row
    if( destCnt % _imageInfo.sliceWidth == 0)
    {

        srcPtr = reinterpret_cast<uint16_T*>( dataSlice.get()  + ( destCnt

/ _imageInfo.sliceWidth )*srcNumBytesPerRow );
        readUnitOffset = readUnitSize;
    }
    uint16_T destSampleVal = 0;

    // Indicates that 11-bits need to be grabbed to compute the value of
this sample completely.
    uint16_T reqNumBitsForSample = bitsPerSample;

    // If 16-bits have been completely processed, then read 16-bits more
from the source.
    if( readUnitOffset == readUnitSize )
    {
        srcSampleVal = *srcPtr++;
        readUnitOffset = 0;
    }

    // Loop until 11-bits have been obtained.
    while( reqNumBitsForSample != 0 )
    {
        // Determine how many bits are available to read in the current
16-bit value read in
        uint16_T numReqBitsAvailInReadUnit = std::min<uint16_T>(
reqNumBitsForSample, readUnitSize - readUnitOffset );

        // Create a bit-mask
        uint16_T bitMask = static_cast<uint16_T>( std::pow( 2.0,
                                                static_cast<double>(
readUnitSize - readUnitOffset ) ) - 1 );

        // Mask and shift the values suitably.
        uint16_T value = srcSampleVal & bitMask;
        value >>= (readUnitSize - (readUnitOffset +
numReqBitsAvailInReadUnit));

        // Add to already extracted bits
        destSampleVal |= (value << (reqNumBitsForSample -
numReqBitsAvailInReadUnit));

        // Updated the ofset and the number of bits required
        readUnitOffset += numReqBitsAvailInReadUnit;
        reqNumBitsForSample -= numReqBitsAvailInReadUnit;

        if( readUnitOffset == readUnitSize )
        {
            srcSampleVal = *srcPtr++;
            readUnitOffset = 0;
        }
    }

    *destPtr++ = destSampleVal;

}

Appreciate any help.

Dinesh