Skip to content

Commit 33e1518

Browse files
authored
Ignore unspecified extra samples for TIFF separate planar configuration (#9514)
2 parents 602acd5 + 007974d commit 33e1518

File tree

3 files changed

+27
-6
lines changed

3 files changed

+27
-6
lines changed
202 Bytes
Binary file not shown.

Tests/test_file_libtiff.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,6 +1055,15 @@ def test_strip_planar_16bit_RGBa(self) -> None:
10551055
with Image.open("Tests/images/tiff_strip_planar_16bit_RGBa.tiff") as im:
10561056
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png")
10571057

1058+
def test_separate_planar_extra_samples(self, tmp_path: Path) -> None:
1059+
out = tmp_path / "temp.tif"
1060+
with Image.open("Tests/images/separate_planar_extra_samples.tiff") as im:
1061+
assert im.mode == "L"
1062+
1063+
im.save(out)
1064+
with Image.open(out) as reloaded:
1065+
assert reloaded.mode == "L"
1066+
10581067
@pytest.mark.parametrize("compression", (None, "jpeg"))
10591068
def test_block_tile_tags(self, compression: str | None, tmp_path: Path) -> None:
10601069
im = hopper()

src/PIL/TiffImagePlugin.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1473,28 +1473,34 @@ def _setup(self) -> None:
14731473
logger.debug("- size: %s", self.size)
14741474

14751475
sample_format = self.tag_v2.get(SAMPLEFORMAT, (1,))
1476-
if len(sample_format) > 1 and max(sample_format) == min(sample_format) == 1:
1476+
if len(sample_format) > 1 and max(sample_format) == min(sample_format):
14771477
# SAMPLEFORMAT is properly per band, so an RGB image will
14781478
# be (1,1,1). But, we don't support per band pixel types,
14791479
# and anything more than one band is a uint8. So, just
14801480
# take the first element. Revisit this if adding support
14811481
# for more exotic images.
1482-
sample_format = (1,)
1482+
sample_format = (sample_format[0],)
14831483

14841484
bps_tuple = self.tag_v2.get(BITSPERSAMPLE, (1,))
14851485
extra_tuple = self.tag_v2.get(EXTRASAMPLES, ())
1486+
samples_per_pixel = self.tag_v2.get(
1487+
SAMPLESPERPIXEL,
1488+
3 if self._compression == "tiff_jpeg" and photo in (2, 6) else 1,
1489+
)
14861490
if photo in (2, 6, 8): # RGB, YCbCr, LAB
14871491
bps_count = 3
14881492
elif photo == 5: # CMYK
14891493
bps_count = 4
14901494
else:
14911495
bps_count = 1
1496+
if self._planar_configuration == 2 and extra_tuple and max(extra_tuple) == 0:
1497+
# If components are stored separately,
1498+
# then unspecified extra components at the end can be ignored
1499+
bps_tuple = bps_tuple[: -len(extra_tuple)]
1500+
samples_per_pixel -= len(extra_tuple)
1501+
extra_tuple = ()
14921502
bps_count += len(extra_tuple)
14931503
bps_actual_count = len(bps_tuple)
1494-
samples_per_pixel = self.tag_v2.get(
1495-
SAMPLESPERPIXEL,
1496-
3 if self._compression == "tiff_jpeg" and photo in (2, 6) else 1,
1497-
)
14981504

14991505
if samples_per_pixel > MAX_SAMPLESPERPIXEL:
15001506
# DOS check, samples_per_pixel can be a Long, and we extend the tuple below
@@ -1762,6 +1768,12 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
17621768
legacy_ifd = im.tag.to_v2()
17631769

17641770
supplied_tags = {**legacy_ifd, **getattr(im, "tag_v2", {})}
1771+
if supplied_tags.get(PLANAR_CONFIGURATION) == 2 and EXTRASAMPLES in supplied_tags:
1772+
# If the image used separate component planes,
1773+
# then EXTRASAMPLES should be ignored when saving contiguously
1774+
if SAMPLESPERPIXEL in supplied_tags:
1775+
supplied_tags[SAMPLESPERPIXEL] -= len(supplied_tags[EXTRASAMPLES])
1776+
del supplied_tags[EXTRASAMPLES]
17651777
for tag in (
17661778
# IFD offset that may not be correct in the saved image
17671779
EXIFIFD,

0 commit comments

Comments
 (0)