From 193f8cb03deab2787f8281bd62e4d92c48a1ceab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Sun, 30 Aug 2026 09:54:17 +0100 Subject: [PATCH] the drawing probe measures near the source, not at the floor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It probed at 96 pixels and got the answer wrong on exactly the file it was written for. Downscaling a logo to 96 resamples its flat areas into gradients, so by the time the probe sees it, it already looks like a photograph. Lossy wins that comparison, and the verdict is applied to every rung above — where the flat areas are still flat and lossless would have won by a wide margin. fofos-logo-saffron came out with a 13,074-byte 160-pixel rung and a 22,432-byte 256-pixel one, beside a 9,144-byte 300-pixel original. Three files larger than the thing they were made from. 480 now, or the source's own width when it is smaller — which is the case that matters, because that is what a logo is. Not the full size: a picture is still recognisably itself at 480, and probing a 4000x6000 photograph losslessly to learn something already obvious is minutes of encoding across a library. Co-Authored-By: Claude Opus 5 (1M context) --- api/derive.ts | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/api/derive.ts b/api/derive.ts index 82b848e..9c5cf83 100644 --- a/api/derive.ts +++ b/api/derive.ts @@ -164,6 +164,9 @@ const THUMB_MAX_WIDTH = 640; */ const SQUARE_TOLERANCE = 0.02; +/** Where the drawing-or-photograph question is asked. See prefersLossless for why not smaller. */ +const PROBE_WIDTH = 480; + /** * Is this a DRAWING rather than a photograph? * @@ -191,8 +194,28 @@ const SQUARE_TOLERANCE = 0.02; * * Ties and failures go to lossy, which is the right default for a library that is mostly photographs. */ -async function prefersLossless(sourcePath: string, staging: string, email: string): Promise { - const probe = LADDER[0]!; +async function prefersLossless( + sourcePath: string, + staging: string, + email: string, + sourceWidth: number, +): Promise { + /* + Measured near the source's own size, NOT at the smallest rung. + + The first attempt probed at 96 pixels and got the answer wrong on exactly the file it was written + for. Downscaling a logo to 96 resamples its flat areas into gradients, so by the time the probe + sees it, it already looks like a photograph — lossy wins the comparison, and that verdict is then + applied to every rung above, where the flat areas are still flat and lossless would have won by a + wide margin. fofos-logo-saffron came out with a 13,074-byte 160-pixel rung beside a 9,144-byte + 300-pixel original. + + 480 rather than the full size: a picture is still recognisably itself at 480 — flat stays flat and + photographic stays photographic — and probing a 4000x6000 photograph losslessly to learn something + already obvious is minutes of encoding across a library. Anything smaller than 480 is probed at its + own size, which is the case that matters, because that is what a logo is. + */ + const probe = Math.min(PROBE_WIDTH, sourceWidth); const lossyPath = join(staging, '.probe-lossy.webp'); const losslessPath = join(staging, '.probe-lossless.webp'); @@ -554,7 +577,7 @@ async function buildImage( photograph want opposite encoders, and getting it wrong on a logo produced a smaller rung two and a half times the size of the original. */ - const lossless = await prefersLossless(master, staging, email); + const lossless = await prefersLossless(master, staging, email, width); const variants: Variant[] = [];