
Thread

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