From 225de5baa84cdbfbc80d22d5b0eec5178b171214 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 2 Jun 2024 22:51:00 +1000 Subject: [PATCH] Use DCTDecode and SMask when saving LA and RGBA images --- Tests/test_file_pdf.py | 10 ++----- docs/handbook/image-file-formats.rst | 3 +- src/PIL/PdfImagePlugin.py | 44 +++++++++++++++------------- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index d39a86565..e7ffdd41a 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -12,7 +12,7 @@ import pytest from PIL import Image, PdfParser, features -from .helper import hopper, mark_if_feature_version, skip_unless_feature +from .helper import hopper, mark_if_feature_version def helper_save_as_pdf(tmp_path: Path, mode: str, **kwargs: Any) -> str: @@ -41,17 +41,11 @@ def helper_save_as_pdf(tmp_path: Path, mode: str, **kwargs: Any) -> str: return outfile -@pytest.mark.parametrize("mode", ("L", "P", "RGB", "CMYK")) +@pytest.mark.parametrize("mode", ("L", "LA", "P", "RGB", "RGBA", "CMYK")) def test_save(tmp_path: Path, mode: str) -> None: helper_save_as_pdf(tmp_path, mode) -@skip_unless_feature("jpg_2000") -@pytest.mark.parametrize("mode", ("LA", "RGBA")) -def test_save_alpha(tmp_path: Path, mode: str) -> None: - helper_save_as_pdf(tmp_path, mode) - - def test_p_alpha(tmp_path: Path) -> None: # Arrange outfile = str(tmp_path / "temp.pdf") diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 1ec972149..fa16f1f7f 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -1585,9 +1585,8 @@ files. Different encoding methods are used, depending on the image mode. * 1 mode images are saved using TIFF encoding, or JPEG encoding if libtiff support is unavailable -* L, RGB and CMYK mode images use JPEG encoding +* L, LA, RGB, RGBA and CMYK mode images use JPEG encoding * P mode images use HEX encoding -* LA and RGBA mode images use JPEG2000 encoding .. _pdf-saving: diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py index 1777f1f20..355eb55fb 100644 --- a/src/PIL/PdfImagePlugin.py +++ b/src/PIL/PdfImagePlugin.py @@ -54,6 +54,7 @@ def _write_image(im, filename, existing_pdf, image_refs): params = None decode = None + smask = None # # Get image characteristics @@ -81,16 +82,16 @@ def _write_image(im, filename, existing_pdf, image_refs): filter = "DCTDecode" dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceGray") procset = "ImageB" # grayscale - elif im.mode == "L": + elif im.mode in ("L", "LA"): filter = "DCTDecode" # params = f"<< /Predictor 15 /Columns {width-2} >>" dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceGray") procset = "ImageB" # grayscale - elif im.mode == "LA": - filter = "JPXDecode" - # params = f"<< /Predictor 15 /Columns {width-2} >>" - procset = "ImageB" # grayscale - dict_obj["SMaskInData"] = 1 + if im.mode == "LA": + smask = im + + im = im.convert("L") + im.encoderinfo = {} elif im.mode == "P": filter = "ASCIIHexDecode" palette = im.getpalette() @@ -103,19 +104,16 @@ def _write_image(im, filename, existing_pdf, image_refs): procset = "ImageI" # indexed color if "transparency" in im.info: - smask = im.convert("LA").getchannel("A") - smask.encoderinfo = {} - - image_ref = _write_image(smask, filename, existing_pdf, image_refs)[0] - dict_obj["SMask"] = image_ref - elif im.mode == "RGB": + smask = im.convert("LA") + elif im.mode in ("RGB", "RGBA"): filter = "DCTDecode" dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceRGB") procset = "ImageC" # color images - elif im.mode == "RGBA": - filter = "JPXDecode" - procset = "ImageC" # color images - dict_obj["SMaskInData"] = 1 + if im.mode == "RGBA": + smask = im + + im = im.convert("RGB") + im.encoderinfo = {} elif im.mode == "CMYK": filter = "DCTDecode" dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceCMYK") @@ -125,6 +123,13 @@ def _write_image(im, filename, existing_pdf, image_refs): msg = f"cannot save mode {im.mode}" raise ValueError(msg) + if smask: + smask = smask.getchannel("A") + smask.encoderinfo = {} + + image_ref = _write_image(smask, filename, existing_pdf, image_refs)[0] + dict_obj["SMask"] = image_ref + # # image @@ -142,9 +147,6 @@ def _write_image(im, filename, existing_pdf, image_refs): ) elif filter == "DCTDecode": Image.SAVE["JPEG"](im, op, filename) - elif filter == "JPXDecode": - del dict_obj["BitsPerComponent"] - Image.SAVE["JPEG2000"](im, op, filename) else: msg = f"unsupported PDF filter ({filter})" raise ValueError(msg) @@ -236,7 +238,9 @@ def _save(im, fp, filename, save_all=False): number_of_pages += im_number_of_pages for i in range(im_number_of_pages): image_refs.append(existing_pdf.next_object_id(0)) - if im.mode == "P" and "transparency" in im.info: + if im.mode in ("LA", "RGBA") or ( + im.mode == "P" and "transparency" in im.info + ): image_refs.append(existing_pdf.next_object_id(0)) page_refs.append(existing_pdf.next_object_id(0))