From 8dac835f53e64302ae1ea6b15b3b89fdad9c14b3 Mon Sep 17 00:00:00 2001 From: Parth Maniar <45627498+officialpm@users.noreply.github.com> Date: Tue, 6 Oct 2026 07:05:24 -0700 Subject: [PATCH 1/3] Support greyscale TIFFs with ExtraSamples 0 or 1 --- src/PIL/TiffImagePlugin.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 9ffb885ceea..f96e5f36c60 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -208,6 +208,10 @@ (MM, 1, (2,), 1, (32,), ()): ("I", "I;32BS"), (II, 1, (3,), 1, (32,), ()): ("F", "F;32F"), (MM, 1, (3,), 1, (32,), ()): ("F", "F;32BF"), + (II, 1, (1,), 1, (8, 8), (0,)): ("LA", "LA"), + (MM, 1, (1,), 1, (8, 8), (0,)): ("LA", "LA"), + (II, 1, (1,), 1, (8, 8), (1,)): ("La", "La"), + (MM, 1, (1,), 1, (8, 8), (1,)): ("La", "La"), (II, 1, (1,), 1, (8, 8), (2,)): ("LA", "LA"), (MM, 1, (1,), 1, (8, 8), (2,)): ("LA", "LA"), (II, 2, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"), From 321a35c71d6f5805190ccceb7d7e3ecc3262fb5c Mon Sep 17 00:00:00 2001 From: Parth Maniar <45627498+officialpm@users.noreply.github.com> Date: Tue, 6 Oct 2026 07:05:42 -0700 Subject: [PATCH 2/3] Add test for greyscale TIFFs with ExtraSamples 0 and 1 --- Tests/test_file_tiff.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 523db1d0a29..d08b12658b4 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -3,6 +3,7 @@ import os import warnings from io import BytesIO +from typing import Literal import pytest @@ -437,6 +438,27 @@ def test_dict(self) -> None: } assert dict(im.tag) == legacy_tags + @pytest.mark.parametrize("extra_samples, mode", ((0, "LA"), (1, "La"), (2, "LA"))) + def test_greyscale_extra_samples(self, extra_samples: int, mode: str) -> None: + out = BytesIO() + Image.new("LA", (8, 8), (128, 200)).save(out, "TIFF") + data = bytearray(out.getvalue()) + + # Change the ExtraSamples tag value in place + order: Literal["little", "big"] = "little" if data[:2] == b"II" else "big" + ifd = int.from_bytes(data[4:8], order) + count = int.from_bytes(data[ifd : ifd + 2], order) + for i in range(count): + entry = ifd + 2 + 12 * i + if int.from_bytes(data[entry : entry + 2], order) == 338: + data[entry + 8 : entry + 10] = extra_samples.to_bytes(2, order) + break + + with Image.open(BytesIO(data)) as im: + assert im.mode == mode + im.load() + assert im.getpixel((0, 0)) == (128, 200) + def test__delitem__(self) -> None: filename = "Tests/images/pil136.tiff" with Image.open(filename) as im: From 2e2d455842ce4ca094ffd909b19e551c27e4f965 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 7 Oct 2026 16:36:01 +1100 Subject: [PATCH 3/3] Read L with unspecified extra samples as L --- Tests/test_file_tiff.py | 22 ---------------------- src/PIL/TiffImagePlugin.py | 4 ++-- src/libImaging/Mode.c | 1 + src/libImaging/Mode.h | 1 + src/libImaging/Unpack.c | 1 + 5 files changed, 5 insertions(+), 24 deletions(-) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index d08b12658b4..523db1d0a29 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -3,7 +3,6 @@ import os import warnings from io import BytesIO -from typing import Literal import pytest @@ -438,27 +437,6 @@ def test_dict(self) -> None: } assert dict(im.tag) == legacy_tags - @pytest.mark.parametrize("extra_samples, mode", ((0, "LA"), (1, "La"), (2, "LA"))) - def test_greyscale_extra_samples(self, extra_samples: int, mode: str) -> None: - out = BytesIO() - Image.new("LA", (8, 8), (128, 200)).save(out, "TIFF") - data = bytearray(out.getvalue()) - - # Change the ExtraSamples tag value in place - order: Literal["little", "big"] = "little" if data[:2] == b"II" else "big" - ifd = int.from_bytes(data[4:8], order) - count = int.from_bytes(data[ifd : ifd + 2], order) - for i in range(count): - entry = ifd + 2 + 12 * i - if int.from_bytes(data[entry : entry + 2], order) == 338: - data[entry + 8 : entry + 10] = extra_samples.to_bytes(2, order) - break - - with Image.open(BytesIO(data)) as im: - assert im.mode == mode - im.load() - assert im.getpixel((0, 0)) == (128, 200) - def test__delitem__(self) -> None: filename = "Tests/images/pil136.tiff" with Image.open(filename) as im: diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index f96e5f36c60..cb299792e0d 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -208,8 +208,8 @@ (MM, 1, (2,), 1, (32,), ()): ("I", "I;32BS"), (II, 1, (3,), 1, (32,), ()): ("F", "F;32F"), (MM, 1, (3,), 1, (32,), ()): ("F", "F;32BF"), - (II, 1, (1,), 1, (8, 8), (0,)): ("LA", "LA"), - (MM, 1, (1,), 1, (8, 8), (0,)): ("LA", "LA"), + (II, 1, (1,), 1, (8, 8), (0,)): ("L", "LX"), + (MM, 1, (1,), 1, (8, 8), (0,)): ("L", "LX"), (II, 1, (1,), 1, (8, 8), (1,)): ("La", "La"), (MM, 1, (1,), 1, (8, 8), (1,)): ("La", "La"), (II, 1, (1,), 1, (8, 8), (2,)): ("LA", "LA"), diff --git a/src/libImaging/Mode.c b/src/libImaging/Mode.c index f3ef7a2c18e..0ef444c9eb4 100644 --- a/src/libImaging/Mode.c +++ b/src/libImaging/Mode.c @@ -144,6 +144,7 @@ const RawModeData RAWMODES[] = { [IMAGING_RAWMODE_K_I] = {"K;I"}, [IMAGING_RAWMODE_LA_16B] = {"LA;16B"}, [IMAGING_RAWMODE_LA_L] = {"LA;L"}, + [IMAGING_RAWMODE_LX] = {"LX"}, [IMAGING_RAWMODE_L_16] = {"L;16"}, [IMAGING_RAWMODE_L_16B] = {"L;16B"}, [IMAGING_RAWMODE_L_2] = {"L;2"}, diff --git a/src/libImaging/Mode.h b/src/libImaging/Mode.h index 39c0eb91994..0b33fb6ce36 100644 --- a/src/libImaging/Mode.h +++ b/src/libImaging/Mode.h @@ -144,6 +144,7 @@ typedef enum { IMAGING_RAWMODE_K_I, IMAGING_RAWMODE_LA_16B, IMAGING_RAWMODE_LA_L, + IMAGING_RAWMODE_LX, IMAGING_RAWMODE_L_16, IMAGING_RAWMODE_L_16B, IMAGING_RAWMODE_L_2, diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c index a515682093d..7623275bd47 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -1582,6 +1582,7 @@ static struct { {IMAGING_MODE_L, IMAGING_RAWMODE_L_R, 8, unpackLR}, {IMAGING_MODE_L, IMAGING_RAWMODE_L_16, 16, unpackL16}, {IMAGING_MODE_L, IMAGING_RAWMODE_L_16B, 16, unpackL16B}, + {IMAGING_MODE_L, IMAGING_RAWMODE_LX, 16, unpackL16B}, /* grayscale w. alpha */ {IMAGING_MODE_LA, IMAGING_RAWMODE_LA, 16, unpackLA},