Support saving PDF with different X and Y resolution.

Add a `dpi` parameter to the PDF save function, which accepts
a tuple with X and Y dpi.
This is useful for converting tiffg3 (fax) images to pdf,
which have split dpi like (204,391), (204,196) or (204,98).
This commit is contained in:
Jasper van der Neut 2023-02-21 10:34:41 +01:00
parent d48dca3dc4
commit 36bcc0a898
3 changed files with 59 additions and 7 deletions

View File

@ -80,6 +80,48 @@ def test_resolution(tmp_path):
assert size == (61.44, 61.44) assert size == (61.44, 61.44)
def test_dpi(tmp_path):
im = hopper()
outfile = str(tmp_path / "temp.pdf")
im.save(outfile, dpi=(75, 150))
with open(outfile, "rb") as fp:
contents = fp.read()
size = tuple(
float(d)
for d in contents.split(b"stream\nq ")[1].split(b" 0 0 cm")[0].split(b" 0 0 ")
)
assert size == (122.88, 61.44)
size = tuple(
float(d) for d in contents.split(b"/MediaBox [ 0 0 ")[1].split(b"]")[0].split()
)
assert size == (122.88, 61.44)
def test_resolution_and_dpi(tmp_path):
im = hopper()
outfile = str(tmp_path / "temp.pdf")
im.save(outfile, resolution=200, dpi=(75, 150))
with open(outfile, "rb") as fp:
contents = fp.read()
size = tuple(
float(d)
for d in contents.split(b"stream\nq ")[1].split(b" 0 0 cm")[0].split(b" 0 0 ")
)
assert size == (122.88, 61.44)
size = tuple(
float(d) for d in contents.split(b"/MediaBox [ 0 0 ")[1].split(b"]")[0].split()
)
assert size == (122.88, 61.44)
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
) )

View File

@ -1497,6 +1497,11 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum
image, will determine the physical dimensions of the page that will be image, will determine the physical dimensions of the page that will be
saved in the PDF. saved in the PDF.
**dpi**
A tuple of (x_resolution, y_resolution), with inches as the resolution
unit. If both the ``resolution`` parameter and the ``dpi`` parameter are
present, ``resolution`` will be ignored.
**title** **title**
The documents title. If not appending to an existing PDF file, this will The documents title. If not appending to an existing PDF file, this will
default to the filename. default to the filename.

View File

@ -53,7 +53,12 @@ def _save(im, fp, filename, save_all=False):
else: else:
existing_pdf = PdfParser.PdfParser(f=fp, filename=filename, mode="w+b") existing_pdf = PdfParser.PdfParser(f=fp, filename=filename, mode="w+b")
resolution = im.encoderinfo.get("resolution", 72.0) x_resolution = y_resolution = im.encoderinfo.get("resolution", 72.0)
dpi = im.encoderinfo.get("dpi")
if dpi:
x_resolution = dpi[0]
y_resolution = dpi[1]
info = { info = {
"title": None "title": None
@ -214,8 +219,8 @@ def _save(im, fp, filename, save_all=False):
stream=stream, stream=stream,
Type=PdfParser.PdfName("XObject"), Type=PdfParser.PdfName("XObject"),
Subtype=PdfParser.PdfName("Image"), Subtype=PdfParser.PdfName("Image"),
Width=width, # * 72.0 / resolution, Width=width, # * 72.0 / x_resolution,
Height=height, # * 72.0 / resolution, Height=height, # * 72.0 / y_resolution,
Filter=filter, Filter=filter,
BitsPerComponent=bits, BitsPerComponent=bits,
Decode=decode, Decode=decode,
@ -235,8 +240,8 @@ def _save(im, fp, filename, save_all=False):
MediaBox=[ MediaBox=[
0, 0,
0, 0,
width * 72.0 / resolution, width * 72.0 / x_resolution,
height * 72.0 / resolution, height * 72.0 / y_resolution,
], ],
Contents=contents_refs[page_number], Contents=contents_refs[page_number],
) )
@ -245,8 +250,8 @@ def _save(im, fp, filename, save_all=False):
# page contents # page contents
page_contents = b"q %f 0 0 %f 0 0 cm /image Do Q\n" % ( page_contents = b"q %f 0 0 %f 0 0 cm /image Do Q\n" % (
width * 72.0 / resolution, width * 72.0 / x_resolution,
height * 72.0 / resolution, height * 72.0 / y_resolution,
) )
existing_pdf.write_obj(contents_refs[page_number], stream=page_contents) existing_pdf.write_obj(contents_refs[page_number], stream=page_contents)