From ed57bc8f625bfd28269ac84b05b12ea2ab69d65c Mon Sep 17 00:00:00 2001 From: BiswasNehaa Date: Fri, 26 Jun 2026 11:27:49 +0530 Subject: [PATCH 1/2] Clarify outdated TODO comment on CMYK palette workaround --- pypdf/generic/_image_xobject.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pypdf/generic/_image_xobject.py b/pypdf/generic/_image_xobject.py index 6a213a2f1..96c6b75ce 100644 --- a/pypdf/generic/_image_xobject.py +++ b/pypdf/generic/_image_xobject.py @@ -300,9 +300,15 @@ def _handle_flate( # gray lookup does not work: it is converted to a similar RGB lookup lookup = b"".join([bytes([b, b, b]) for b in lookup]) mode = "RGB" - # TODO: https://github.com/py-pdf/pypdf/pull/2039 - # this is a work around until PIL is able to process CMYK images elif mode == "CMYK": + # Pillow could not unpack CMYK palettes until 10.1.0 + # (https://github.com/python-pillow/Pillow/issues/7309, + # fixed by https://github.com/python-pillow/Pillow/pull/7310). + # pypdf currently supports Pillow>=8.0.0, so this manual + # CMYK -> RGB conversion needs to stay until that floor is + # raised to 10.1.0. Note: this rounds slightly differently + # than Pillow's native unpacker (off-by-one on some channels), + # so output is not byte-identical between Pillow versions. _rgb = [] for _c, _m, _y, _k in ( lookup[n : n + 4] for n in range(0, 4 * (len(lookup) // 4), 4) From 81c4851dfb96395e527a118a40f8910ad4fe3136 Mon Sep 17 00:00:00 2001 From: BiswasNehaa Date: Fri, 26 Jun 2026 11:33:00 +0530 Subject: [PATCH 2/2] Detect image format from Pillow instead of guessing from filter type --- pypdf/generic/_image_xobject.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/pypdf/generic/_image_xobject.py b/pypdf/generic/_image_xobject.py index 96c6b75ce..9a98b959b 100644 --- a/pypdf/generic/_image_xobject.py +++ b/pypdf/generic/_image_xobject.py @@ -542,18 +542,23 @@ def _apply_alpha( obj_as_text, ) elif lfilters in (FT.LZW_DECODE, FT.ASCII_85_DECODE): - # I'm not sure if the following logic is correct. - # There might not be any relationship between the filters and the - # extension - if lfilters == FT.LZW_DECODE: - image_format = "TIFF" - extension = ".tiff" # mime_type = "image/tiff" - else: - image_format = "PNG" - extension = ".png" # mime_type = "image/png" + # LZW-encoded image data is most commonly TIFF, and ASCII85-encoded + # image data is most commonly PNG, but the filter alone does not + # guarantee the underlying format. Try to open the data directly + # first and trust Pillow's own format detection; only fall back to + # guessing from the filter type if Pillow cannot identify the image + # (in which case it is interpreted as raw, undecoded pixel data). try: img = Image.open(BytesIO(data), formats=("TIFF", "PNG")) + image_format = cast(str, img.format) + extension = "." + image_format.lower() except UnidentifiedImageError: + if lfilters == FT.LZW_DECODE: + image_format = "TIFF" + extension = ".tiff" # mime_type = "image/tiff" + else: + image_format = "PNG" + extension = ".png" # mime_type = "image/png" img = _image_from_bytes(mode, size, data) elif lfilters == FT.DCT_DECODE: img, image_format, extension = Image.open(BytesIO(data)), "JPEG", ".jpg"