2008.10.31 17:10 "[Tiff] Data ordering for planar config separate", by Richard Nolde

2008.11.10 18:40 "[Tiff] Writing scanlines with PLANARCONFIG == SEPARATE", by Richard Nolde

I've managed to get tiffcrop to support reading and writing TIFF files with bit depths from 1 to 32 for RGB files but there appears to be a limitation in the library that only it can only support PLANNARCONFIG == SEPARATE if the strips are organized with all the scanlines for a given sample packed into one or more strips before any scanlines for the next sample are written, eg:

Strip1
Scanline1 RRRRRRRRRRRR
Scanline2 RRRRRRRRRRRR
...
ScanlineN RRRRRRRRRRRR
Strip2
ScanlineN + 1 RRRRRRRRRRRR
ScanlineN + 2 RRRRRRRRRRRR

Strip3
Scanline1 GGGGGGGGGGG
Scanline2 GGGGGGGGGGG
...
ScanlineN GGGGGGGGGGG
Strip4
ScanlineN + 1 GGGGGGGGGG
ScanlineN + 2 GGGGGGGGGG

Strip5
Scanline1 BBBBBBBBBBBB
Scanline2 BBBBBBBBBBBB
....
ScanlineN BBBBBBBBBBBB
Strip6
Scanline N + 1 BBBBBBBBBB
Scanline N + 2 BBBBBBBBBB

I think this is due to the following code in tif_write.c, and/or other places that setup the stripsperimage value and he stripbytecounts for each strip.

tif_write.c
...
int
TIFFWriteScanline(TIFF* tif, tdata_t buf, uint32 row, tsample_t sample)
    /*
     * Calculate strip and check for crossings.
     */
    if (td->td_planarconfig == PLANARCONFIG_SEPARATE) {
        if (sample >= td->td_samplesperpixel) {
            TIFFErrorExt(tif->tif_clientdata, tif->tif_name,
                "%d: Sample out of range, max %d",
                sample, td->td_samplesperpixel);
            return (-1);
        }
        strip = sample*td->td_stripsperimage + row/td->td_rowsperstrip;
    } else
        strip = row / td->td_rowsperstrip;
    /*

     * Check strip array to make sure there's space. We don't support
     * dynamically growing files that have data organized in separate
     * bitplanes because it's too painful.  In that case we require that
     * the imagelength be set properly before the first write (so that the
     * strips array will be fully allocated above).
     */
    if (strip >= td->td_nstrips && !TIFFGrowStrips(tif, 1, module))

        return (-1);
    if (strip != tif->tif_curstrip) {

        /*
         * Changing strips -- flush any data present.
         */
        if (!TIFFFlushData(tif))
            return (-1);
        tif->tif_curstrip = strip;
        /*
         * Watch out for a growing image.  The value of strips/image
         * will initially be 1 (since it can't be deduced until the
         * imagelength is known).
         */
        if (strip >= td->td_stripsperimage && imagegrew)

            td->td_stripsperimage =
                TIFFhowmany(td->td_imagelength,td->td_rowsperstrip);
        tif->tif_row =
            (strip % td->td_stripsperimage) * td->td_rowsperstrip;
        if ((tif->tif_flags & TIFF_CODERSETUP) == 0) {
            if (!(*tif->tif_setupencode)(tif))
                return (-1);
            tif->tif_flags |= TIFF_CODERSETUP;
        }

        tif->tif_rawcc = 0;
        tif->tif_rawcp = tif->tif_rawdata;

        if( td->td_stripbytecount[strip] > 0 )
        {
            /* if we are writing over existing tiles, zero length */
            td->td_stripbytecount[strip] = 0;

            /* this forces TIFFAppendToStrip() to do a seek */
            tif->tif_curoff = 0;
        }
....

Since the data are compressed/stored on a per scanline basis, I wanted to write the data for each sample of a given row of the image to successive scan lines within one strip to speed up access when only a part of the image is being used.

Strip 1
Scanline 1 RRRRRRRRR
Scanline 2 GGGGGGGG
Scanline 3 BBBBBBBBB

The current code in tif_write.c computes a strip value of 0, 3, 6 for samples 0, 1, 2 in the initial row of my test image, but it zeroes the stripbyte count every time it gets to the code at the end of the excerpt above, which makes the resulting file invalid. Looking at the StripOffsetts, there are nine values at 3 * ImageWidth apart even though the debugger shows a value of three stripsperimage when inside this function (at least for the first few rows of the image).

(gdb) p td->td_stripsperimage
$88 = 3

At the end of the three passes through TIFFWriteScanline for the first row, the following strip byte counts were shown

(gdb) p *td->td_stripbytecount@9
$91 = {73, 0, 0, 73, 0, 0, 73, 0, 0}

but the fourth iteration of the function for sample 0 of row two reset the strip byte count for the first strip to zero before writing out that sample.

(gdb) p *td->td_stripbytecount@9
$91 = {0, 0, 0, 73, 0, 0, 73, 0, 0}

tiffdump

/tmp/tiger-rgb-strip-planar-08.tiff/tmp/tiger-rgb-strip-planar-08.tiff:
Magic: 0x4949 <little-endian> Version: 0x2a
Directory 0: offset 666 (0x29a) next 0 (0)
ImageWidth (256) SHORT (3) 1<73>
ImageLength (257) SHORT (3) 1<76>
BitsPerSample (258) SHORT (3) 3<8 8 8>
Compression (259) SHORT (3) 1<1>
Photometric (262) SHORT (3) 1<2>
DocumentName (269) ASCII (2) 30<tiger-rgb-strip-contig-0 ...>
ImageDescription (270) ASCII (2) 53< Image generated by GPL ...>
StripOffsets (273) LONG (4) 9<8 227 446 81 300 519 154 373 592>
Orientation (274) SHORT (3) 1<1>
SamplesPerPixel (277) SHORT (3) 1<3>
RowsPerStrip (278) SHORT (3) 1<37>
StripByteCounts (279) LONG (4) 9<0 0 0 0 0 0 0 0 0>
XResolution (282) RATIONAL (5) 1<72>
YResolution (283) RATIONAL (5) 1<72>
PlanarConfig (284) SHORT (3) 1<2>
ResolutionUnit (296) SHORT (3) 1<1>
PageNumber (297) SHORT (3) 2<1 0>

(305) ASCII (2) 65<GraphicsMagick 1.3 unrel ...>
SampleFormat (339) SHORT (3) 3<1 1 1>

Question 1: Is it valid to write scanlines (each scanline containing only values of one sample plane) interleaved by sample within a single strip of a TIFF image for PLANNARCONFIG SEPARATE? Question 2 Is there anyway to do this within the current configuration of libtiff?

Question 3 Can libtiff be modified to handle this option without breaking existing applications?

Richard Nolde

tiffcrop author