Handle Ghostscript rasterization with DPI below 10
Ghostscript may fail when asked to rasterize at very low DPI values (below 10 on either axis). This adds a workaround that uses a minimum of 10 DPI for the Ghostscript call, then resizes the output image to match the dimensions that would have resulted from the original low DPI request. Fixes #1612
This commit is contained in:
@@ -127,6 +127,19 @@ def rasterize_pdf(
|
||||
if not page_dpi:
|
||||
page_dpi = raster_dpi
|
||||
|
||||
# Ghostscript may fail with very low DPI values (below 10). If the requested
|
||||
# DPI is too low, use a minimum of 10 DPI and resize the output afterward.
|
||||
MIN_RASTER_DPI = 10
|
||||
needs_low_dpi_resize = (
|
||||
raster_dpi.x < MIN_RASTER_DPI or raster_dpi.y < MIN_RASTER_DPI
|
||||
)
|
||||
if needs_low_dpi_resize:
|
||||
effective_dpi = Resolution(
|
||||
max(raster_dpi.x, MIN_RASTER_DPI), max(raster_dpi.y, MIN_RASTER_DPI)
|
||||
)
|
||||
else:
|
||||
effective_dpi = raster_dpi
|
||||
|
||||
args_gs = (
|
||||
[
|
||||
GS,
|
||||
@@ -137,7 +150,7 @@ def rasterize_pdf(
|
||||
f'-sDEVICE={raster_device}',
|
||||
f'-dFirstPage={pageno}',
|
||||
f'-dLastPage={pageno}',
|
||||
f'-r{raster_dpi.x:f}x{raster_dpi.y:f}',
|
||||
f'-r{effective_dpi.x:f}x{effective_dpi.y:f}',
|
||||
]
|
||||
+ (['-dUseCropBox'] if use_cropbox else [])
|
||||
+ (['-dFILTERVECTOR'] if filter_vector else [])
|
||||
@@ -173,6 +186,16 @@ def rasterize_pdf(
|
||||
|
||||
try:
|
||||
with Image.open(output_file) as im:
|
||||
if needs_low_dpi_resize:
|
||||
# Resize to the dimensions that would have resulted from the
|
||||
# original low DPI request
|
||||
scale_x = raster_dpi.x / effective_dpi.x
|
||||
scale_y = raster_dpi.y / effective_dpi.y
|
||||
new_size = (
|
||||
max(1, int(round(im.width * scale_x))),
|
||||
max(1, int(round(im.height * scale_y))),
|
||||
)
|
||||
im = im.resize(new_size, Image.Resampling.LANCZOS)
|
||||
if rotation is not None:
|
||||
log.debug("Rotating output by %i", rotation)
|
||||
# rotation is a clockwise angle and Image.ROTATE_* is
|
||||
|
||||
@@ -81,6 +81,62 @@ def test_rasterize_rotated(francais, outdir, caplog):
|
||||
assert im.info['dpi'] == forced_dpi.flip_axis()
|
||||
|
||||
|
||||
def test_rasterize_low_dpi(francais, outdir):
|
||||
"""Test that very low DPI values (below 10) produce correctly sized output.
|
||||
|
||||
Ghostscript may fail with DPI values below 10. The workaround renders at
|
||||
a minimum of 10 DPI and resizes the output to match the expected dimensions.
|
||||
"""
|
||||
path, pdf = francais
|
||||
page_size_pts = (pdf.pages[0].mediabox[2], pdf.pages[0].mediabox[3])
|
||||
assert pdf.pages[0].mediabox[0] == pdf.pages[0].mediabox[1] == 0
|
||||
page_size = (float(page_size_pts[0]) / 72, float(page_size_pts[1]) / 72)
|
||||
|
||||
# Request a very small output (DPI below 10 on both axes)
|
||||
target_size = (5, 3)
|
||||
forced_dpi = Resolution(72.0, 72.0)
|
||||
|
||||
rasterize_pdf(
|
||||
path,
|
||||
outdir / 'out_low_dpi.png',
|
||||
raster_device=GhostscriptRasterDevice.PNGMONO,
|
||||
raster_dpi=Resolution(
|
||||
target_size[0] / page_size[0], target_size[1] / page_size[1]
|
||||
),
|
||||
page_dpi=forced_dpi,
|
||||
)
|
||||
|
||||
with Image.open(outdir / 'out_low_dpi.png') as im:
|
||||
assert im.size == target_size
|
||||
assert im.info['dpi'] == forced_dpi
|
||||
|
||||
|
||||
def test_rasterize_low_dpi_one_axis(francais, outdir):
|
||||
"""Test low DPI on only one axis produces correctly sized output."""
|
||||
path, pdf = francais
|
||||
page_size_pts = (pdf.pages[0].mediabox[2], pdf.pages[0].mediabox[3])
|
||||
assert pdf.pages[0].mediabox[0] == pdf.pages[0].mediabox[1] == 0
|
||||
page_size = (float(page_size_pts[0]) / 72, float(page_size_pts[1]) / 72)
|
||||
|
||||
# Request low DPI on X axis only (below 10), normal on Y axis
|
||||
target_size = (5, 50)
|
||||
forced_dpi = Resolution(72.0, 72.0)
|
||||
|
||||
rasterize_pdf(
|
||||
path,
|
||||
outdir / 'out_low_dpi_x.png',
|
||||
raster_device=GhostscriptRasterDevice.PNGMONO,
|
||||
raster_dpi=Resolution(
|
||||
target_size[0] / page_size[0], target_size[1] / page_size[1]
|
||||
),
|
||||
page_dpi=forced_dpi,
|
||||
)
|
||||
|
||||
with Image.open(outdir / 'out_low_dpi_x.png') as im:
|
||||
assert im.size == target_size
|
||||
assert im.info['dpi'] == forced_dpi
|
||||
|
||||
|
||||
def test_gs_render_failure(resources, outpdf, caplog):
|
||||
exitcode = run_ocrmypdf_api(
|
||||
resources / 'blank.pdf',
|
||||
|
||||
Reference in New Issue
Block a user