Source code for pagescan.output
"""PDF output generation."""
from __future__ import annotations
import io
import logging
from pathlib import Path
import cv2
import img2pdf
import numpy as np
from PIL import Image
from pagescan.config import ScanConfig
logger = logging.getLogger(__name__)
[docs]
def save_pdf(image: np.ndarray, output_path: str, config: ScanConfig | None = None) -> None:
"""Save image as a single-page PDF.
The image is the finished canvas produced by ``place_on_canvas`` — the
corrected document centred on a white page of ``output_width`` x
``output_height`` pixels. It is JPEG-encoded and embedded losslessly; the
physical PDF page size is derived from those pixel dimensions and
``output_dpi`` (e.g. 2480x3508 px @ 300 DPI -> A4, 2550x3300 @ 300 -> US
Letter). The page therefore always matches the configured canvas, and
img2pdf never distorts the aspect ratio.
"""
if config is None:
config = ScanConfig()
if image.ndim == 2:
pil_img = Image.fromarray(image, mode='L')
else:
rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
pil_img = Image.fromarray(rgb)
buf = io.BytesIO()
dpi = config.output_dpi
pil_img.save(buf, format='JPEG', quality=config.jpeg_quality,
optimize=True, dpi=(dpi, dpi))
buf.seek(0)
# Page size follows the embedded image's DPI (set above), so the PDF page
# matches the configured canvas instead of being forced to A4.
pdf_bytes = img2pdf.convert(buf.read())
Path(output_path).parent.mkdir(parents=True, exist_ok=True)
Path(output_path).write_bytes(pdf_bytes)
[docs]
def save_image(image: np.ndarray, output_path: str) -> None:
"""Save image as JPEG or PNG (inferred from extension)."""
Path(output_path).parent.mkdir(parents=True, exist_ok=True)
cv2.imwrite(output_path, image)