web: the eyedropper reads a colour and the mixer moves that hue band

This commit is contained in:
2026-09-18 17:41:45 +07:00
parent 8a889db069
commit 1b71c0196f
11 changed files with 449 additions and 7 deletions
+19
View File
@@ -31,6 +31,25 @@ export async function resizedJpeg(bytes: Uint8Array, maxDim: number, quality = 0
}
}
// The colour under one point of a photo, for the HSL mixer's eyedropper.
// `fx`/`fy` are fractions of the photo (0..1, from the click position over the
// <img>), so the caller needs no knowledge of the stage's zoom, pan or box.
// One pixel, read straight off the RENDER — the swatch then shows the colour
// the user is actually looking at, grade and all, which is what makes the
// band the eyedropper lands on the one they meant.
export function samplePixel(bitmap: ImageBitmap, fx: number, fy: number): { r: number; g: number; b: number } {
const sx = Math.min(bitmap.width - 1, Math.max(0, Math.floor(fx * bitmap.width)));
const sy = Math.min(bitmap.height - 1, Math.max(0, Math.floor(fy * bitmap.height)));
const canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
const ctx = canvas.getContext('2d', { willReadFrequently: true });
if (!ctx) return { r: 0, g: 0, b: 0 };
ctx.drawImage(bitmap, sx, sy, 1, 1, 0, 0, 1, 1);
const [r, g, b] = ctx.getImageData(0, 0, 1, 1).data;
return { r, g, b };
}
// Uploads are capped by the API (and by nginx in front of it), so a phone's
// 12MP JPEG has to shrink before it goes up — the same downscale the preview
// uses. The ORIGINAL bytes come back untouched (same reference) when the photo