From f3684c14674fc9895377b2bcf2557d76bd603244 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Mon, 16 Mar 2026 16:43:50 -0700 Subject: [PATCH] fix: downscale images to 1024px max before dithering Full-res images (1743x1786) took 45s for error diffusion, hitting Cloudflare's timeout. Downscaling to 1024px max keeps it under 10s. Co-Authored-By: Claude Opus 4.6 --- backend/app/services/dither_service.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/backend/app/services/dither_service.py b/backend/app/services/dither_service.py index 99826be..cd061dc 100644 --- a/backend/app/services/dither_service.py +++ b/backend/app/services/dither_service.py @@ -14,6 +14,9 @@ from app.schemas.dither import DitherAlgorithm, PaletteMode logger = logging.getLogger(__name__) +# Max dimension for dithering input — keeps processing under Cloudflare timeout +_MAX_DITHER_DIM = 1024 + # In-memory cache: sha256(slug+params) → (png_bytes, metadata) _MAX_CACHE = 200 _dither_cache: OrderedDict[str, tuple[bytes, dict]] = OrderedDict() @@ -32,6 +35,15 @@ def _evict(cache: OrderedDict, max_size: int = _MAX_CACHE): cache.popitem(last=False) +def _downscale(image: Image.Image, max_dim: int = _MAX_DITHER_DIM) -> Image.Image: + """Downscale image if either dimension exceeds max_dim.""" + if image.width <= max_dim and image.height <= max_dim: + return image + scale = max_dim / max(image.width, image.height) + new_size = (int(image.width * scale), int(image.height * scale)) + return image.resize(new_size, Image.LANCZOS) + + def _hex_to_rgb(hex_str: str) -> tuple[int, int, int]: h = hex_str.lstrip("#") return (int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)) @@ -163,7 +175,7 @@ def dither_design( meta["cached"] = True return png_bytes, meta - image = Image.open(image_path) + image = _downscale(Image.open(image_path)) palette = build_palette(image, palette_mode, num_colors, custom_colors) dithered = apply_dither(image, palette, algorithm, threshold, order) @@ -207,7 +219,7 @@ def generate_color_separations( _separation_cache.move_to_end(key) return _separation_cache[key] - image = Image.open(image_path) + image = _downscale(Image.open(image_path)) palette = build_palette(image, palette_mode, num_colors, spot_colors) dithered = apply_dither(image, palette, algorithm)