04/27 Updated inspection PDF export with compress, low images to reduce file sizes

This commit is contained in:
2026-04-27 17:24:58 -04:00
parent 5f5390e00b
commit 74fd9f7ce6
+58 -1
View File
@@ -17,6 +17,12 @@ import os
import json
from datetime import datetime
try:
from PIL import Image as PILImage
_PIL_AVAILABLE = True
except ImportError:
_PIL_AVAILABLE = False
from reportlab.lib.pagesizes import letter
from reportlab.lib import colors
from reportlab.lib.units import inch
@@ -94,6 +100,51 @@ def _build_styles():
STYLES = _build_styles()
# ── Image compression helper ──────────────────────────────────────────────────
# Max pixel dimension for any side of an image embedded in the PDF.
# Phone cameras produce 1248 MP images; 800px is plenty for a printed report.
_IMG_MAX_PX = 800
# JPEG quality for re-encoded images (095). 55 is visually acceptable for
# a printed report and reduces a typical phone photo by ~90% vs the original.
_IMG_JPEG_QUALITY = 55
def _compress_image(src_path: str) -> io.BytesIO | None:
"""
Load an image from disk, resize it to fit within _IMG_MAX_PX on any side,
re-encode as JPEG at _IMG_JPEG_QUALITY, and return a BytesIO buffer.
Returns None if PIL is unavailable or the image cannot be processed,
signalling the caller to fall back to the original file path.
"""
if not _PIL_AVAILABLE:
return None
try:
img = PILImage.open(src_path)
# Strip transparency — JPEG does not support alpha channels.
# Convert palette / RGBA / LA → RGB before saving.
if img.mode in ('RGBA', 'LA', 'P'):
background = PILImage.new('RGB', img.size, (255, 255, 255))
if img.mode == 'P':
img = img.convert('RGBA')
background.paste(img, mask=img.split()[-1] if img.mode in ('RGBA', 'LA') else None)
img = background
elif img.mode != 'RGB':
img = img.convert('RGB')
# Resize proportionally so neither dimension exceeds _IMG_MAX_PX.
img.thumbnail((_IMG_MAX_PX, _IMG_MAX_PX), PILImage.LANCZOS)
buf = io.BytesIO()
img.save(buf, format='JPEG', quality=_IMG_JPEG_QUALITY, optimize=True)
buf.seek(0)
return buf
except Exception:
return None
# ── Header / footer callbacks ─────────────────────────────────────────────────
def _on_page(canvas, doc, title, generated_at):
@@ -417,7 +468,13 @@ def _form_fields_section(form_fields, form_data, static_folder):
# File existence already verified in the skip gate above
img_path = os.path.join(static_folder, val)
try:
val_p = RLImage(img_path,
# Attempt to compress the image before embedding.
# _compress_image() resizes to _IMG_MAX_PX and re-encodes
# as JPEG at _IMG_JPEG_QUALITY — typically 8595% smaller
# than the original phone photo.
compressed = _compress_image(img_path)
img_src = compressed if compressed is not None else img_path
val_p = RLImage(img_src,
width=min(cell_w - 12, 1.4 * inch),
height=1.0 * inch,
kind='proportional')