From e0bad975ec005c2c5862acc713e071c8f48e252e Mon Sep 17 00:00:00 2001 From: Matt Edholm Date: Wed, 6 May 2026 16:43:44 -0400 Subject: [PATCH] fix: replace contrastStretchImage with normalizeImage for auto-levels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit contrastStretchImage's parameters are quantum-range intensity thresholds in IM7, not pixel counts as the original code assumed. Empirically any arg >= 1 collapsed low-tonal-range photos to pure white — verified by probing image 16's mid-photo pixel which goes from (80,69,59) to (255,255,255) with cs(100,100), cs(1382,1382), or cs(3840,3840) alike. normalizeImage() uses the image's actual histogram percentiles (default 2% black/white clip) and produces gentle, correct stretching: same pixel goes (80,69,59) to (89,72,59) — a small contrast bump that preserves the photo's tonal information instead of obliterating it. Existing on-disk bins were rendered with the broken stretch; running app:rerender-assets after this deploy regenerates them with the new pipeline. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/MessageHandler/RenderImageMessageHandler.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/MessageHandler/RenderImageMessageHandler.php b/src/MessageHandler/RenderImageMessageHandler.php index 723123e..516a50d 100644 --- a/src/MessageHandler/RenderImageMessageHandler.php +++ b/src/MessageHandler/RenderImageMessageHandler.php @@ -107,12 +107,14 @@ final class RenderImageMessageHandler // Per-pixel adjustments run on the PHOTO before the white canvas is // composited under it — otherwise the bars dominate the histogram and - // the contrast stretch over-clips the photo content to white. + // would skew any auto-leveling toward white. - // Auto-levels: stretch the tonal range, clipping 1% at each end. - // Fixes underexposed/dark photos so the full palette range is used. - $photoPixels = $imagick->getImageWidth() * $imagick->getImageHeight(); - $imagick->contrastStretchImage((int) ($photoPixels * 0.01), (int) ($photoPixels * 0.01)); + // Auto-levels via normalizeImage (default 2%/2% clip). Empirically + // contrastStretchImage's args are quantum-range intensity thresholds, + // not pixel counts, and even small ints (>= ~1) collapse low-tonal- + // range photos to pure white. normalizeImage uses the image's actual + // histogram percentiles and produces gentle, correct stretching. + $imagick->normalizeImage(); // Boost saturation 130%. Dark desaturated photos otherwise map almost // entirely to BLACK with scattered noise dots from error diffusion.