From 84cb30d7a7388ff984dcf284e32e7c4fb2abb44b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 30 Mar 2026 19:42:07 +1100 Subject: [PATCH] For separate planar configuration, ignore unspecified extra components --- Tests/images/separate_planar_extra_samples.tiff | Bin 0 -> 202 bytes Tests/test_file_libtiff.py | 4 ++++ src/PIL/TiffImagePlugin.py | 14 ++++++++++---- 3 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 Tests/images/separate_planar_extra_samples.tiff diff --git a/Tests/images/separate_planar_extra_samples.tiff b/Tests/images/separate_planar_extra_samples.tiff new file mode 100644 index 0000000000000000000000000000000000000000..be51a7570ee92e579e41d178e6646c645eed56e1 GIT binary patch literal 202 zcmebD)MC(KU|^`2^Y-*YMg|5RCWZg?Rl5&lHTea_Ty9E~Xak~+Qv5qN-Hqm9U|?is z04icI0%AraHWQG|1Qg={LT0Eq2awMOWrOqxGO~cx90IaMq2eGtVo)~7OmQF^B&Gty XGDvEIplqNpLoieg6Idn4P6z-1gKZPS literal 0 HcmV?d00001 diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 6f20900e4..ea0550a5f 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -1055,6 +1055,10 @@ class TestFileLibTiff(LibTiffTestCase): 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") + def test_separate_planar_extra_samples(self) -> None: + with Image.open("Tests/images/separate_planar_extra_samples.tiff") as im: + assert im.mode == "L" + @pytest.mark.parametrize("compression", (None, "jpeg")) def test_block_tile_tags(self, compression: str | None, tmp_path: Path) -> None: im = hopper() diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index f11b6ce97..669dc8a3e 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1483,18 +1483,24 @@ class TiffImageFile(ImageFile.ImageFile): bps_tuple = self.tag_v2.get(BITSPERSAMPLE, (1,)) extra_tuple = self.tag_v2.get(EXTRASAMPLES, ()) + samples_per_pixel = self.tag_v2.get( + SAMPLESPERPIXEL, + 3 if self._compression == "tiff_jpeg" and photo in (2, 6) else 1, + ) if photo in (2, 6, 8): # RGB, YCbCr, LAB bps_count = 3 elif photo == 5: # CMYK bps_count = 4 else: bps_count = 1 + if self._planar_configuration == 2 and extra_tuple and max(extra_tuple) == 0: + # If components are stored separately, + # then unspecified extra components at the end can be ignored + bps_tuple = bps_tuple[: -len(extra_tuple)] + samples_per_pixel -= len(extra_tuple) + extra_tuple = () bps_count += len(extra_tuple) bps_actual_count = len(bps_tuple) - samples_per_pixel = self.tag_v2.get( - SAMPLESPERPIXEL, - 3 if self._compression == "tiff_jpeg" and photo in (2, 6) else 1, - ) if samples_per_pixel > MAX_SAMPLESPERPIXEL: # DOS check, samples_per_pixel can be a Long, and we extend the tuple below