Use DCTDecode and SMask when saving LA and RGBA images

This commit is contained in:
Andrew Murray 2024-06-02 22:51:00 +10:00
parent 4f4b0bc748
commit 225de5baa8
3 changed files with 27 additions and 30 deletions

View File

@ -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")

View File

@ -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:

View File

@ -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))