From f128d47e150aa91db74c06092290b9cd0a0b04aa Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 12 Mar 2024 19:30:23 +1100 Subject: [PATCH] Added reading of JPEG2000 palettes --- Tests/test_file_jpeg2k.py | 10 ++++++++++ src/PIL/Jpeg2KImagePlugin.py | 15 ++++++++++++--- src/libImaging/Jpeg2KDecode.c | 2 ++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index b7f8350c775..bd114aa1181 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -364,6 +364,16 @@ def test_subsampling_decode(name: str) -> None: assert_image_similar(im, expected, epsilon) +@pytest.mark.skipif( + not os.path.exists(EXTRA_DIR), reason="Extra image files not installed" +) +def test_pclr() -> None: + with Image.open(f"{EXTRA_DIR}/issue104_jpxstream.jp2") as im: + assert im.mode == "P" + assert len(im.palette.colors) == 256 + assert im.palette.colors[(255, 255, 255)] == 0 + + def test_comment() -> None: with Image.open("Tests/images/comment.jp2") as im: assert im.info["comment"] == b"Created by OpenJPEG version 2.5.0" diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py index 4b778a0d33a..27f8fbbd0b5 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -19,7 +19,7 @@ import os import struct -from . import Image, ImageFile, _binary +from . import Image, ImageFile, ImagePalette, _binary class BoxReader: @@ -162,6 +162,7 @@ def _parse_jp2_header(fp): bpc = None nc = None dpi = None # 2-tuple of DPI info, or None + palette = None while header.has_next_box(): tbox = header.next_box_type() @@ -179,6 +180,14 @@ def _parse_jp2_header(fp): mode = "RGB" elif nc == 4: mode = "RGBA" + elif tbox == b"pclr" and mode in ("L", "LA"): + ne, npc = header.read_fields(">HB") + bitdepths = header.read_fields(">" + ("B" * npc)) + if max(bitdepths) <= 8: + palette = ImagePalette.ImagePalette() + for i in range(ne): + palette.getcolor(header.read_fields(">" + ("B" * npc))) + mode = "P" if mode == "L" else "PA" elif tbox == b"res ": res = header.read_boxes() while res.has_next_box(): @@ -195,7 +204,7 @@ def _parse_jp2_header(fp): msg = "Malformed JP2 header" raise SyntaxError(msg) - return size, mode, mimetype, dpi + return size, mode, mimetype, dpi, palette ## @@ -217,7 +226,7 @@ def _open(self): if sig == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a": self.codec = "jp2" header = _parse_jp2_header(self.fp) - self._size, self._mode, self.custom_mimetype, dpi = header + self._size, self._mode, self.custom_mimetype, dpi, self.palette = header if dpi is not None: self.info["dpi"] = dpi if self.fp.read(12).endswith(b"jp2c\xff\x4f\xff\x51"): diff --git a/src/libImaging/Jpeg2KDecode.c b/src/libImaging/Jpeg2KDecode.c index cff30e2d0bf..13f363422da 100644 --- a/src/libImaging/Jpeg2KDecode.c +++ b/src/libImaging/Jpeg2KDecode.c @@ -615,6 +615,8 @@ j2ku_sycca_rgba( static const struct j2k_decode_unpacker j2k_unpackers[] = { {"L", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_l}, + {"P", OPJ_CLRSPC_SRGB, 1, 0, j2ku_gray_l}, + {"PA", OPJ_CLRSPC_SRGB, 2, 0, j2ku_graya_la}, {"I;16", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i}, {"I;16B", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i}, {"LA", OPJ_CLRSPC_GRAY, 2, 0, j2ku_graya_la},