Skip to content

Commit f566c8a

Browse files
committed
Add support for reading TIFFs with PlanarConfiguration=2
1 parent e1e77ff commit f566c8a

10 files changed

+338
-47
lines changed
30.8 KB
Binary file not shown.
36.4 KB
Binary file not shown.
151 KB
Binary file not shown.
33.7 KB
Binary file not shown.
40.1 KB
Binary file not shown.
156 KB
Binary file not shown.

Tests/test_file_libtiff.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,52 @@ def test_tiled_ycbcr_jpeg_2x2_sampling(self):
843843
with Image.open(infile) as im:
844844
assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5)
845845

846+
@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
847+
def test_strip_planar_rgb(self):
848+
# gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \
849+
# tiff_strip_raw.tif tiff_strip_planar_lzw.tiff
850+
infile = "Tests/images/tiff_strip_planar_lzw.tiff"
851+
with Image.open(infile) as im:
852+
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
853+
854+
@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
855+
def test_tiled_planar_rgb(self):
856+
# gdal_translate -co TILED=yes -co INTERLEAVE=BAND -co COMPRESS=LZW \
857+
# tiff_tiled_raw.tif tiff_tiled_planar_lzw.tiff
858+
infile = "Tests/images/tiff_tiled_planar_lzw.tiff"
859+
with Image.open(infile) as im:
860+
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
861+
862+
@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
863+
def test_tiled_planar_16bit_RGB(self):
864+
# gdal_translate -co TILED=yes -co INTERLEAVE=BAND -co COMPRESS=LZW \
865+
# tiff_16bit_RGB.tiff tiff_tiled_planar_16bit_RGB.tiff
866+
with Image.open("Tests/images/tiff_tiled_planar_16bit_RGB.tiff") as im:
867+
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png")
868+
869+
@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
870+
def test_strip_planar_16bit_RGB(self):
871+
# gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \
872+
# tiff_16bit_RGB.tiff tiff_strip_planar_16bit_RGB.tiff
873+
with Image.open("Tests/images/tiff_strip_planar_16bit_RGB.tiff") as im:
874+
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png")
875+
876+
@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
877+
def test_tiled_planar_16bit_RGBa(self):
878+
# gdal_translate -co TILED=yes \
879+
# -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \
880+
# tiff_16bit_RGBa.tiff tiff_tiled_planar_16bit_RGBa.tiff
881+
with Image.open("Tests/images/tiff_tiled_planar_16bit_RGBa.tiff") as im:
882+
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png")
883+
884+
@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
885+
def test_strip_planar_16bit_RGBa(self):
886+
# gdal_translate -co TILED=no \
887+
# -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \
888+
# tiff_16bit_RGBa.tiff tiff_strip_planar_16bit_RGBa.tiff
889+
with Image.open("Tests/images/tiff_strip_planar_16bit_RGBa.tiff") as im:
890+
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png")
891+
846892
@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
847893
def test_old_style_jpeg(self):
848894
infile = "Tests/images/old-style-jpeg-compression.tif"

Tests/test_lib_pack.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,23 @@ def test_RGB(self):
320320
self.assert_unpack("RGB", "G", 1, (0, 1, 0), (0, 2, 0), (0, 3, 0))
321321
self.assert_unpack("RGB", "B", 1, (0, 0, 1), (0, 0, 2), (0, 0, 3))
322322

323+
self.assert_unpack("RGB", "R;16B", 2, (1, 0, 0), (3, 0, 0), (5, 0, 0))
324+
self.assert_unpack("RGB", "G;16B", 2, (0, 1, 0), (0, 3, 0), (0, 5, 0))
325+
self.assert_unpack("RGB", "B;16B", 2, (0, 0, 1), (0, 0, 3), (0, 0, 5))
326+
327+
self.assert_unpack("RGB", "R;16L", 2, (2, 0, 0), (4, 0, 0), (6, 0, 0))
328+
self.assert_unpack("RGB", "G;16L", 2, (0, 2, 0), (0, 4, 0), (0, 6, 0))
329+
self.assert_unpack("RGB", "B;16L", 2, (0, 0, 2), (0, 0, 4), (0, 0, 6))
330+
331+
if sys.byteorder == "little":
332+
self.assert_unpack("RGB", "R;16N", 2, (2, 0, 0), (4, 0, 0), (6, 0, 0))
333+
self.assert_unpack("RGB", "G;16N", 2, (0, 2, 0), (0, 4, 0), (0, 6, 0))
334+
self.assert_unpack("RGB", "B;16N", 2, (0, 0, 2), (0, 0, 4), (0, 0, 6))
335+
else:
336+
self.assert_unpack("RGB", "R;16N", 2, (1, 0, 0), (3, 0, 0), (5, 0, 0))
337+
self.assert_unpack("RGB", "G;16N", 2, (0, 1, 0), (0, 3, 0), (0, 5, 0))
338+
self.assert_unpack("RGB", "B;16N", 2, (0, 0, 1), (0, 0, 3), (0, 0, 5))
339+
323340
def test_RGBA(self):
324341
self.assert_unpack("RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6))
325342
self.assert_unpack(
@@ -450,6 +467,43 @@ def test_RGBA(self):
450467
self.assert_unpack("RGBA", "B", 1, (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0))
451468
self.assert_unpack("RGBA", "A", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3))
452469

470+
self.assert_unpack("RGBA", "R;16B", 2, (1, 0, 0, 0), (3, 0, 0, 0), (5, 0, 0, 0))
471+
self.assert_unpack("RGBA", "G;16B", 2, (0, 1, 0, 0), (0, 3, 0, 0), (0, 5, 0, 0))
472+
self.assert_unpack("RGBA", "B;16B", 2, (0, 0, 1, 0), (0, 0, 3, 0), (0, 0, 5, 0))
473+
self.assert_unpack("RGBA", "A;16B", 2, (0, 0, 0, 1), (0, 0, 0, 3), (0, 0, 0, 5))
474+
475+
self.assert_unpack("RGBA", "R;16L", 2, (2, 0, 0, 0), (4, 0, 0, 0), (6, 0, 0, 0))
476+
self.assert_unpack("RGBA", "G;16L", 2, (0, 2, 0, 0), (0, 4, 0, 0), (0, 6, 0, 0))
477+
self.assert_unpack("RGBA", "B;16L", 2, (0, 0, 2, 0), (0, 0, 4, 0), (0, 0, 6, 0))
478+
self.assert_unpack("RGBA", "A;16L", 2, (0, 0, 0, 2), (0, 0, 0, 4), (0, 0, 0, 6))
479+
480+
if sys.byteorder == "little":
481+
self.assert_unpack(
482+
"RGBA", "R;16N", 2, (2, 0, 0, 0), (4, 0, 0, 0), (6, 0, 0, 0)
483+
)
484+
self.assert_unpack(
485+
"RGBA", "G;16N", 2, (0, 2, 0, 0), (0, 4, 0, 0), (0, 6, 0, 0)
486+
)
487+
self.assert_unpack(
488+
"RGBA", "B;16N", 2, (0, 0, 2, 0), (0, 0, 4, 0), (0, 0, 6, 0)
489+
)
490+
self.assert_unpack(
491+
"RGBA", "A;16N", 2, (0, 0, 0, 2), (0, 0, 0, 4), (0, 0, 0, 6)
492+
)
493+
else:
494+
self.assert_unpack(
495+
"RGBA", "R;16N", 2, (1, 0, 0, 0), (3, 0, 0, 0), (5, 0, 0, 0)
496+
)
497+
self.assert_unpack(
498+
"RGBA", "G;16N", 2, (0, 1, 0, 0), (0, 3, 0, 0), (0, 5, 0, 0)
499+
)
500+
self.assert_unpack(
501+
"RGBA", "B;16N", 2, (0, 0, 1, 0), (0, 0, 3, 0), (0, 0, 5, 0)
502+
)
503+
self.assert_unpack(
504+
"RGBA", "A;16N", 2, (0, 0, 0, 1), (0, 0, 0, 3), (0, 0, 0, 5)
505+
)
506+
453507
def test_RGBa(self):
454508
self.assert_unpack(
455509
"RGBa", "RGBa", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)

src/libImaging/TiffDecode.c

Lines changed: 115 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ int ImagingLibTiffInit(ImagingCodecState state, int fp, uint32 offset) {
181181
}
182182

183183

184-
int ReadTile(TIFF* tiff, UINT32 col, UINT32 row, UINT32* buffer) {
184+
int ReadTile(TIFF* tiff, UINT32 col, UINT32 row, UINT8 plane, UINT32* buffer) {
185185
uint16 photometric = 0;
186186

187187
TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric);
@@ -228,7 +228,7 @@ int ReadTile(TIFF* tiff, UINT32 col, UINT32 row, UINT32* buffer) {
228228
return 0;
229229
}
230230

231-
if (TIFFReadTile(tiff, (tdata_t)buffer, col, row, 0, 0) == -1) {
231+
if (TIFFReadTile(tiff, (tdata_t)buffer, col, row, 0, plane) == -1) {
232232
TRACE(("Decode Error, Tile at %dx%d\n", col, row));
233233
return -1;
234234
}
@@ -238,7 +238,7 @@ int ReadTile(TIFF* tiff, UINT32 col, UINT32 row, UINT32* buffer) {
238238
return 0;
239239
}
240240

241-
int ReadStrip(TIFF* tiff, UINT32 row, UINT32* buffer) {
241+
int ReadStrip(TIFF* tiff, UINT32 row, UINT8 plane, UINT32* buffer) {
242242
uint16 photometric = 0; // init to not PHOTOMETRIC_YCBCR
243243
TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric);
244244

@@ -259,6 +259,8 @@ int ReadStrip(TIFF* tiff, UINT32 row, UINT32* buffer) {
259259
if (TIFFRGBAImageOK(tiff, emsg) && TIFFRGBAImageBegin(&img, tiff, 0, emsg)) {
260260
TRACE(("Initialized RGBAImage\n"));
261261

262+
// Using TIFFRGBAImageGet instead of TIFFReadRGBAStrip
263+
// just to get strip in TOP_LEFT orientation
262264
img.req_orientation = ORIENTATION_TOPLEFT;
263265
img.row_offset = row;
264266
img.col_offset = 0;
@@ -281,7 +283,7 @@ int ReadStrip(TIFF* tiff, UINT32 row, UINT32* buffer) {
281283
return 0;
282284
}
283285

284-
if (TIFFReadEncodedStrip(tiff, TIFFComputeStrip(tiff, row, 0), (tdata_t)buffer, -1) == -1) {
286+
if (TIFFReadEncodedStrip(tiff, TIFFComputeStrip(tiff, row, plane), (tdata_t)buffer, -1) == -1) {
285287
TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, row, 0)));
286288
return -1;
287289
}
@@ -294,6 +296,10 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, Py_
294296
char *filename = "tempfile.tif";
295297
char *mode = "r";
296298
TIFF *tiff;
299+
uint16 photometric = 0; // init to not PHOTOMETRIC_YCBCR
300+
UINT8 planarconfig;
301+
UINT8 planes = 1;
302+
ImagingShuffler unpackers[4];
297303

298304
/* buffer is the encoded file, bytes is the length of the encoded file */
299305
/* it all ends up in state->buffer, which is a uint8* from Imaging.h */
@@ -350,8 +356,38 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, Py_
350356
rv = TIFFSetSubDirectory(tiff, ifdoffset);
351357
if (!rv){
352358
TRACE(("error in TIFFSetSubDirectory"));
359+
state->errcode = IMAGING_CODEC_BROKEN;
360+
TIFFClose(tiff);
361+
return -1;
362+
}
363+
}
364+
365+
TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric);
366+
TIFFGetFieldDefaulted(tiff, TIFFTAG_PLANARCONFIG, &planarconfig);
367+
// YCbCr data is read as RGB by libtiff and we don't need to worry about planar storage in that case
368+
// if number of bands is 1, there is no difference with contig case
369+
if (planarconfig == PLANARCONFIG_SEPARATE && im->bands > 1 && photometric != PHOTOMETRIC_YCBCR) {
370+
uint16 bps = 8;
371+
372+
TIFFGetFieldDefaulted(tiff, TIFFTAG_BITSPERSAMPLE, &bps);
373+
if (bps != 8 && bps != 16) {
374+
TRACE(("Invalid value for bits per sample: %d\n", bps));
375+
state->errcode = IMAGING_CODEC_BROKEN;
376+
TIFFClose(tiff);
353377
return -1;
354378
}
379+
380+
planes = im->bands;
381+
382+
// We'll pick appropriate set of unpackers depending on planar_configuration
383+
// It does not matter if data is RGB(A), CMYK or LUV really,
384+
// we just copy it plane by plane
385+
unpackers[0] = ImagingFindUnpacker("RGBA", bps == 16 ? "R;16N" : "R", NULL);
386+
unpackers[1] = ImagingFindUnpacker("RGBA", bps == 16 ? "G;16N" : "G", NULL);
387+
unpackers[2] = ImagingFindUnpacker("RGBA", bps == 16 ? "B;16N" : "B", NULL);
388+
unpackers[3] = ImagingFindUnpacker("RGBA", bps == 16 ? "A;16N" : "A", NULL);
389+
} else {
390+
unpackers[0] = state->shuffle;
355391
}
356392

357393
if (TIFFIsTiled(tiff)) {
@@ -370,19 +406,19 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, Py_
370406
}
371407

372408
// We could use TIFFTileSize, but for YCbCr data it returns subsampled data size
373-
row_byte_size = (tile_width * state->bits + 7) / 8;
409+
row_byte_size = (tile_width * state->bits / planes + 7) / 8;
374410

375411
/* overflow check for realloc */
376412
if (INT_MAX / row_byte_size < tile_length) {
377413
state->errcode = IMAGING_CODEC_MEMORY;
378414
TIFFClose(tiff);
379415
return -1;
380416
}
381-
417+
382418
state->bytes = row_byte_size * tile_length;
383419

384420
if (TIFFTileSize(tiff) > state->bytes) {
385-
// If the strip size as expected by LibTiff isn't what we're expecting, abort.
421+
// If the tile size as expected by LibTiff isn't what we're expecting, abort.
386422
state->errcode = IMAGING_CODEC_MEMORY;
387423
TIFFClose(tiff);
388424
return -1;
@@ -402,29 +438,33 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, Py_
402438
TRACE(("TIFFTileSize: %d\n", state->bytes));
403439

404440
for (y = state->yoff; y < state->ysize; y += tile_length) {
405-
for (x = state->xoff; x < state->xsize; x += tile_width) {
406-
if (ReadTile(tiff, x, y, (UINT32*) state->buffer) == -1) {
407-
TRACE(("Decode Error, Tile at %dx%d\n", x, y));
408-
state->errcode = IMAGING_CODEC_BROKEN;
409-
TIFFClose(tiff);
410-
return -1;
411-
}
412-
413-
TRACE(("Read tile at %dx%d; \n\n", x, y));
414-
415-
current_tile_width = min((INT32) tile_width, state->xsize - x);
416-
417-
// iterate over each line in the tile and stuff data into image
418-
for (tile_y = 0; tile_y < min((INT32) tile_length, state->ysize - y); tile_y++) {
419-
TRACE(("Writing tile data at %dx%d using tile_width: %d; \n", tile_y + y, x, current_tile_width));
420-
421-
// UINT8 * bbb = state->buffer + tile_y * row_byte_size;
422-
// TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));
423-
424-
state->shuffle((UINT8*) im->image[tile_y + y] + x * im->pixelsize,
425-
state->buffer + tile_y * row_byte_size,
426-
current_tile_width
427-
);
441+
UINT8 plane;
442+
for (plane = 0; plane < planes; plane++) {
443+
ImagingShuffler shuffler = unpackers[plane];
444+
for (x = state->xoff; x < state->xsize; x += tile_width) {
445+
if (ReadTile(tiff, x, y, plane, (UINT32*) state->buffer) == -1) {
446+
TRACE(("Decode Error, Tile at %dx%d\n", x, y));
447+
state->errcode = IMAGING_CODEC_BROKEN;
448+
TIFFClose(tiff);
449+
return -1;
450+
}
451+
452+
TRACE(("Read tile at %dx%d; \n\n", x, y));
453+
454+
current_tile_width = min((INT32) tile_width, state->xsize - x);
455+
456+
// iterate over each line in the tile and stuff data into image
457+
for (tile_y = 0; tile_y < min((INT32) tile_length, state->ysize - y); tile_y++) {
458+
TRACE(("Writing tile data at %dx%d using tile_width: %d; \n", tile_y + y, x, current_tile_width));
459+
460+
// UINT8 * bbb = state->buffer + tile_y * row_byte_size;
461+
// TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));
462+
463+
shuffler((UINT8*) im->image[tile_y + y] + x * im->pixelsize,
464+
state->buffer + tile_y * row_byte_size,
465+
current_tile_width
466+
);
467+
}
428468
}
429469
}
430470
}
@@ -441,7 +481,7 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, Py_
441481
TRACE(("RowsPerStrip: %u \n", rows_per_strip));
442482

443483
// We could use TIFFStripSize, but for YCbCr data it returns subsampled data size
444-
row_byte_size = (state->xsize * state->bits + 7) / 8;
484+
row_byte_size = (state->xsize * state->bits / planes + 7) / 8;
445485

446486
/* overflow check for realloc */
447487
if (INT_MAX / row_byte_size < rows_per_strip) {
@@ -476,26 +516,55 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, Py_
476516
state->buffer = new_data;
477517

478518
for (; state->y < state->ysize; state->y += rows_per_strip) {
479-
if (ReadStrip(tiff, state->y, (UINT32 *)state->buffer) == -1) {
480-
TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0)));
481-
state->errcode = IMAGING_CODEC_BROKEN;
482-
TIFFClose(tiff);
483-
return -1;
484-
}
519+
UINT8 plane;
520+
for (plane = 0; plane < planes; plane++) {
521+
ImagingShuffler shuffler = unpackers[plane];
522+
if (ReadStrip(tiff, state->y, plane, (UINT32 *)state->buffer) == -1) {
523+
TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0)));
524+
state->errcode = IMAGING_CODEC_BROKEN;
525+
TIFFClose(tiff);
526+
return -1;
527+
}
485528

486-
TRACE(("Decoded strip for row %d \n", state->y));
529+
TRACE(("Decoded strip for row %d \n", state->y));
487530

488-
// iterate over each row in the strip and stuff data into image
489-
for (strip_row = 0; strip_row < min((INT32) rows_per_strip, state->ysize - state->y); strip_row++) {
531+
// iterate over each row in the strip and stuff data into image
532+
for (strip_row = 0; strip_row < min((INT32) rows_per_strip, state->ysize - state->y); strip_row++) {
490533
TRACE(("Writing data into line %d ; \n", state->y + strip_row));
491534

492-
// UINT8 * bbb = state->buffer + strip_row * (state->bytes / rows_per_strip);
493-
// TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));
535+
// UINT8 * bbb = state->buffer + strip_row * (state->bytes / rows_per_strip);
536+
// TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));
537+
538+
shuffler((UINT8*) im->image[state->y + state->yoff + strip_row] +
539+
state->xoff * im->pixelsize,
540+
state->buffer + strip_row * row_byte_size,
541+
state->xsize);
542+
}
543+
}
544+
}
545+
}
546+
547+
548+
// Check if raw mode was RGBa and it was stored on separate planes
549+
// so we have to convert it to RGBA
550+
if (planes > 3 && strcmp(im->mode, "RGBA") == 0) {
551+
uint16 extrasamples;
552+
uint16* sampleinfo;
553+
ImagingShuffler shuffle;
554+
INT32 y;
555+
556+
TIFFGetFieldDefaulted(tiff, TIFFTAG_EXTRASAMPLES, &extrasamples, &sampleinfo);
557+
558+
if (extrasamples >= 1 &&
559+
(sampleinfo[0] == EXTRASAMPLE_UNSPECIFIED || sampleinfo[0] == EXTRASAMPLE_ASSOCALPHA)
560+
) {
561+
shuffle = ImagingFindUnpacker("RGBA", "RGBa", NULL);
562+
563+
for (y = state->yoff; y < state->ysize; y++) {
564+
UINT8* ptr = (UINT8*) im->image[y + state->yoff] +
565+
state->xoff * im->pixelsize;
494566

495-
state->shuffle((UINT8*) im->image[state->y + state->yoff + strip_row] +
496-
state->xoff * im->pixelsize,
497-
state->buffer + strip_row * row_byte_size,
498-
state->xsize);
567+
shuffle(ptr, ptr, state->xsize);
499568
}
500569
}
501570
}

0 commit comments

Comments
 (0)