Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for reading TIFFs with PlanarConfiguration=2 #4641

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added Tests/images/tiff_strip_planar_16bit_RGB.tiff
Binary file not shown.
Binary file added Tests/images/tiff_strip_planar_16bit_RGBa.tiff
Binary file not shown.
Binary file added Tests/images/tiff_strip_planar_lzw.tiff
Binary file not shown.
Binary file added Tests/images/tiff_tiled_planar_16bit_RGB.tiff
Binary file not shown.
Binary file added Tests/images/tiff_tiled_planar_16bit_RGBa.tiff
Binary file not shown.
Binary file added Tests/images/tiff_tiled_planar_lzw.tiff
Binary file not shown.
46 changes: 46 additions & 0 deletions Tests/test_file_libtiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,52 @@ def test_tiled_ycbcr_jpeg_2x2_sampling(self):
with Image.open(infile) as im:
assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5)

@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
def test_strip_planar_rgb(self):
# gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \
# tiff_strip_raw.tif tiff_strip_planar_lzw.tiff
infile = "Tests/images/tiff_strip_planar_lzw.tiff"
with Image.open(infile) as im:
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")

@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
def test_tiled_planar_rgb(self):
# gdal_translate -co TILED=yes -co INTERLEAVE=BAND -co COMPRESS=LZW \
# tiff_tiled_raw.tif tiff_tiled_planar_lzw.tiff
infile = "Tests/images/tiff_tiled_planar_lzw.tiff"
with Image.open(infile) as im:
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")

@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
def test_tiled_planar_16bit_RGB(self):
# gdal_translate -co TILED=yes -co INTERLEAVE=BAND -co COMPRESS=LZW \
# tiff_16bit_RGB.tiff tiff_tiled_planar_16bit_RGB.tiff
with Image.open("Tests/images/tiff_tiled_planar_16bit_RGB.tiff") as im:
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png")

@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
def test_strip_planar_16bit_RGB(self):
# gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \
# tiff_16bit_RGB.tiff tiff_strip_planar_16bit_RGB.tiff
with Image.open("Tests/images/tiff_strip_planar_16bit_RGB.tiff") as im:
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png")

@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
def test_tiled_planar_16bit_RGBa(self):
# gdal_translate -co TILED=yes \
# -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \
# tiff_16bit_RGBa.tiff tiff_tiled_planar_16bit_RGBa.tiff
with Image.open("Tests/images/tiff_tiled_planar_16bit_RGBa.tiff") as im:
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png")

@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
def test_strip_planar_16bit_RGBa(self):
# gdal_translate -co TILED=no \
# -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \
# tiff_16bit_RGBa.tiff tiff_strip_planar_16bit_RGBa.tiff
with Image.open("Tests/images/tiff_strip_planar_16bit_RGBa.tiff") as im:
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png")

@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
def test_old_style_jpeg(self):
infile = "Tests/images/old-style-jpeg-compression.tif"
Expand Down
54 changes: 54 additions & 0 deletions Tests/test_lib_pack.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,23 @@ def test_RGB(self):
self.assert_unpack("RGB", "G", 1, (0, 1, 0), (0, 2, 0), (0, 3, 0))
self.assert_unpack("RGB", "B", 1, (0, 0, 1), (0, 0, 2), (0, 0, 3))

self.assert_unpack("RGB", "R;16B", 2, (1, 0, 0), (3, 0, 0), (5, 0, 0))
self.assert_unpack("RGB", "G;16B", 2, (0, 1, 0), (0, 3, 0), (0, 5, 0))
self.assert_unpack("RGB", "B;16B", 2, (0, 0, 1), (0, 0, 3), (0, 0, 5))

self.assert_unpack("RGB", "R;16L", 2, (2, 0, 0), (4, 0, 0), (6, 0, 0))
self.assert_unpack("RGB", "G;16L", 2, (0, 2, 0), (0, 4, 0), (0, 6, 0))
self.assert_unpack("RGB", "B;16L", 2, (0, 0, 2), (0, 0, 4), (0, 0, 6))

if sys.byteorder == "little":
self.assert_unpack("RGB", "R;16N", 2, (2, 0, 0), (4, 0, 0), (6, 0, 0))
self.assert_unpack("RGB", "G;16N", 2, (0, 2, 0), (0, 4, 0), (0, 6, 0))
self.assert_unpack("RGB", "B;16N", 2, (0, 0, 2), (0, 0, 4), (0, 0, 6))
else:
self.assert_unpack("RGB", "R;16N", 2, (1, 0, 0), (3, 0, 0), (5, 0, 0))
self.assert_unpack("RGB", "G;16N", 2, (0, 1, 0), (0, 3, 0), (0, 5, 0))
self.assert_unpack("RGB", "B;16N", 2, (0, 0, 1), (0, 0, 3), (0, 0, 5))

def test_RGBA(self):
self.assert_unpack("RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6))
self.assert_unpack(
Expand Down Expand Up @@ -450,6 +467,43 @@ def test_RGBA(self):
self.assert_unpack("RGBA", "B", 1, (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0))
self.assert_unpack("RGBA", "A", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3))

self.assert_unpack("RGBA", "R;16B", 2, (1, 0, 0, 0), (3, 0, 0, 0), (5, 0, 0, 0))
self.assert_unpack("RGBA", "G;16B", 2, (0, 1, 0, 0), (0, 3, 0, 0), (0, 5, 0, 0))
self.assert_unpack("RGBA", "B;16B", 2, (0, 0, 1, 0), (0, 0, 3, 0), (0, 0, 5, 0))
self.assert_unpack("RGBA", "A;16B", 2, (0, 0, 0, 1), (0, 0, 0, 3), (0, 0, 0, 5))

self.assert_unpack("RGBA", "R;16L", 2, (2, 0, 0, 0), (4, 0, 0, 0), (6, 0, 0, 0))
self.assert_unpack("RGBA", "G;16L", 2, (0, 2, 0, 0), (0, 4, 0, 0), (0, 6, 0, 0))
self.assert_unpack("RGBA", "B;16L", 2, (0, 0, 2, 0), (0, 0, 4, 0), (0, 0, 6, 0))
self.assert_unpack("RGBA", "A;16L", 2, (0, 0, 0, 2), (0, 0, 0, 4), (0, 0, 0, 6))

if sys.byteorder == "little":
self.assert_unpack(
"RGBA", "R;16N", 2, (2, 0, 0, 0), (4, 0, 0, 0), (6, 0, 0, 0)
)
self.assert_unpack(
"RGBA", "G;16N", 2, (0, 2, 0, 0), (0, 4, 0, 0), (0, 6, 0, 0)
)
self.assert_unpack(
"RGBA", "B;16N", 2, (0, 0, 2, 0), (0, 0, 4, 0), (0, 0, 6, 0)
)
self.assert_unpack(
"RGBA", "A;16N", 2, (0, 0, 0, 2), (0, 0, 0, 4), (0, 0, 0, 6)
)
else:
self.assert_unpack(
"RGBA", "R;16N", 2, (1, 0, 0, 0), (3, 0, 0, 0), (5, 0, 0, 0)
)
self.assert_unpack(
"RGBA", "G;16N", 2, (0, 1, 0, 0), (0, 3, 0, 0), (0, 5, 0, 0)
)
self.assert_unpack(
"RGBA", "B;16N", 2, (0, 0, 1, 0), (0, 0, 3, 0), (0, 0, 5, 0)
)
self.assert_unpack(
"RGBA", "A;16N", 2, (0, 0, 0, 1), (0, 0, 0, 3), (0, 0, 0, 5)
)

def test_RGBa(self):
self.assert_unpack(
"RGBa", "RGBa", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)
Expand Down
161 changes: 115 additions & 46 deletions src/libImaging/TiffDecode.c
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ int ImagingLibTiffInit(ImagingCodecState state, int fp, uint32 offset) {
}


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

TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric);
Expand Down Expand Up @@ -228,7 +228,7 @@ int ReadTile(TIFF* tiff, UINT32 col, UINT32 row, UINT32* buffer) {
return 0;
}

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

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

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

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

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

/* buffer is the encoded file, bytes is the length of the encoded file */
/* it all ends up in state->buffer, which is a uint8* from Imaging.h */
Expand Down Expand Up @@ -350,8 +356,38 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, Py_
rv = TIFFSetSubDirectory(tiff, ifdoffset);
if (!rv){
TRACE(("error in TIFFSetSubDirectory"));
state->errcode = IMAGING_CODEC_BROKEN;
TIFFClose(tiff);
return -1;
}
}

TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric);
TIFFGetFieldDefaulted(tiff, TIFFTAG_PLANARCONFIG, &planarconfig);
// YCbCr data is read as RGB by libtiff and we don't need to worry about planar storage in that case
// if number of bands is 1, there is no difference with contig case
if (planarconfig == PLANARCONFIG_SEPARATE && im->bands > 1 && photometric != PHOTOMETRIC_YCBCR) {
uint16 bps = 8;

TIFFGetFieldDefaulted(tiff, TIFFTAG_BITSPERSAMPLE, &bps);
if (bps != 8 && bps != 16) {
TRACE(("Invalid value for bits per sample: %d\n", bps));
state->errcode = IMAGING_CODEC_BROKEN;
TIFFClose(tiff);
return -1;
}

planes = im->bands;

// We'll pick appropriate set of unpackers depending on planar_configuration
// It does not matter if data is RGB(A), CMYK or LUV really,
// we just copy it plane by plane
unpackers[0] = ImagingFindUnpacker("RGBA", bps == 16 ? "R;16N" : "R", NULL);
unpackers[1] = ImagingFindUnpacker("RGBA", bps == 16 ? "G;16N" : "G", NULL);
unpackers[2] = ImagingFindUnpacker("RGBA", bps == 16 ? "B;16N" : "B", NULL);
unpackers[3] = ImagingFindUnpacker("RGBA", bps == 16 ? "A;16N" : "A", NULL);
} else {
unpackers[0] = state->shuffle;
}

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

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

/* overflow check for realloc */
if (INT_MAX / row_byte_size < tile_length) {
state->errcode = IMAGING_CODEC_MEMORY;
TIFFClose(tiff);
return -1;
}

state->bytes = row_byte_size * tile_length;

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

for (y = state->yoff; y < state->ysize; y += tile_length) {
for (x = state->xoff; x < state->xsize; x += tile_width) {
if (ReadTile(tiff, x, y, (UINT32*) state->buffer) == -1) {
TRACE(("Decode Error, Tile at %dx%d\n", x, y));
state->errcode = IMAGING_CODEC_BROKEN;
TIFFClose(tiff);
return -1;
}

TRACE(("Read tile at %dx%d; \n\n", x, y));

current_tile_width = min((INT32) tile_width, state->xsize - x);

// iterate over each line in the tile and stuff data into image
for (tile_y = 0; tile_y < min((INT32) tile_length, state->ysize - y); tile_y++) {
TRACE(("Writing tile data at %dx%d using tile_width: %d; \n", tile_y + y, x, current_tile_width));

// UINT8 * bbb = state->buffer + tile_y * row_byte_size;
// TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));

state->shuffle((UINT8*) im->image[tile_y + y] + x * im->pixelsize,
state->buffer + tile_y * row_byte_size,
current_tile_width
);
UINT8 plane;
for (plane = 0; plane < planes; plane++) {
ImagingShuffler shuffler = unpackers[plane];
for (x = state->xoff; x < state->xsize; x += tile_width) {
if (ReadTile(tiff, x, y, plane, (UINT32*) state->buffer) == -1) {
TRACE(("Decode Error, Tile at %dx%d\n", x, y));
state->errcode = IMAGING_CODEC_BROKEN;
TIFFClose(tiff);
return -1;
}

TRACE(("Read tile at %dx%d; \n\n", x, y));

current_tile_width = min((INT32) tile_width, state->xsize - x);

// iterate over each line in the tile and stuff data into image
for (tile_y = 0; tile_y < min((INT32) tile_length, state->ysize - y); tile_y++) {
TRACE(("Writing tile data at %dx%d using tile_width: %d; \n", tile_y + y, x, current_tile_width));

// UINT8 * bbb = state->buffer + tile_y * row_byte_size;
// TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));

shuffler((UINT8*) im->image[tile_y + y] + x * im->pixelsize,
state->buffer + tile_y * row_byte_size,
current_tile_width
);
}
}
}
}
Expand All @@ -441,7 +481,7 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, Py_
TRACE(("RowsPerStrip: %u \n", rows_per_strip));

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

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

for (; state->y < state->ysize; state->y += rows_per_strip) {
if (ReadStrip(tiff, state->y, (UINT32 *)state->buffer) == -1) {
TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0)));
state->errcode = IMAGING_CODEC_BROKEN;
TIFFClose(tiff);
return -1;
}
UINT8 plane;
for (plane = 0; plane < planes; plane++) {
ImagingShuffler shuffler = unpackers[plane];
if (ReadStrip(tiff, state->y, plane, (UINT32 *)state->buffer) == -1) {
TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0)));
state->errcode = IMAGING_CODEC_BROKEN;
TIFFClose(tiff);
return -1;
}

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

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

// UINT8 * bbb = state->buffer + strip_row * (state->bytes / rows_per_strip);
// TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));
// UINT8 * bbb = state->buffer + strip_row * (state->bytes / rows_per_strip);
// TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3]));

shuffler((UINT8*) im->image[state->y + state->yoff + strip_row] +
state->xoff * im->pixelsize,
state->buffer + strip_row * row_byte_size,
state->xsize);
}
}
}
}


// Check if raw mode was RGBa and it was stored on separate planes
// so we have to convert it to RGBA
if (planes > 3 && strcmp(im->mode, "RGBA") == 0) {
uint16 extrasamples;
uint16* sampleinfo;
ImagingShuffler shuffle;
INT32 y;

TIFFGetFieldDefaulted(tiff, TIFFTAG_EXTRASAMPLES, &extrasamples, &sampleinfo);

if (extrasamples >= 1 &&
(sampleinfo[0] == EXTRASAMPLE_UNSPECIFIED || sampleinfo[0] == EXTRASAMPLE_ASSOCALPHA)
) {
shuffle = ImagingFindUnpacker("RGBA", "RGBa", NULL);

for (y = state->yoff; y < state->ysize; y++) {
UINT8* ptr = (UINT8*) im->image[y + state->yoff] +
state->xoff * im->pixelsize;

state->shuffle((UINT8*) im->image[state->y + state->yoff + strip_row] +
state->xoff * im->pixelsize,
state->buffer + strip_row * row_byte_size,
state->xsize);
shuffle(ptr, ptr, state->xsize);
}
}
}
Expand Down
Loading