fix(fe): render the photo + mask overlay on the Mask step
The mask screen showed only controls and a pixel-count line - no photo, so the owner had nothing to adjust the sheet/sensitivity against. Draw the calibration photo to a canvas and paint removed (non-slab) pixels as a light-gray checkerboard (CAPTURE-45/50), redrawing when the overlay settles. Verified in Chromium: mask step now shows the photo with the checkerboard.
This commit is contained in:
@@ -18,6 +18,56 @@
|
||||
$: measure = kind === 'measure' ? cap : null;
|
||||
$: results = measure?.results ?? null;
|
||||
|
||||
// Mask preview: the calibration photo with removed (non-slab) pixels shown
|
||||
// as a light-gray checkerboard (CAPTURE-45/50). Redrawn when the overlay
|
||||
// settles. The photo element is bound so its natural size is available.
|
||||
let maskCanvas: HTMLCanvasElement;
|
||||
let maskImg: HTMLImageElement;
|
||||
let maskObjUrl = '';
|
||||
|
||||
$: if (kind === 'mask' && cap.source) {
|
||||
if (maskObjUrl) URL.revokeObjectURL(maskObjUrl);
|
||||
maskObjUrl = URL.createObjectURL(cap.source.blob);
|
||||
}
|
||||
|
||||
$: if (kind === 'mask' && maskImg?.complete && maskImg.naturalWidth > 0 && overlay) {
|
||||
drawMaskPreview();
|
||||
}
|
||||
|
||||
function drawMaskPreview() {
|
||||
const canvas = maskCanvas;
|
||||
if (!canvas || !maskImg || !overlay) return;
|
||||
const w = maskImg.naturalWidth;
|
||||
const h = maskImg.naturalHeight;
|
||||
// Cap the canvas at a sensible size for performance; keep aspect.
|
||||
const maxDim = 900;
|
||||
const scale = Math.min(1, maxDim / Math.max(w, h));
|
||||
canvas.width = Math.round(w * scale);
|
||||
canvas.height = Math.round(h * scale);
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
ctx.drawImage(maskImg, 0, 0, canvas.width, canvas.height);
|
||||
// Downsample the full-res bitset to the canvas grid and checker removed px.
|
||||
const bitset = overlay.bitset;
|
||||
const cw = canvas.width;
|
||||
const ch = canvas.height;
|
||||
const cell = 6; // checker cell px
|
||||
const imgData = ctx.getImageData(0, 0, cw, ch);
|
||||
const px = imgData.data;
|
||||
for (let y = 0; y < ch; y++) {
|
||||
for (let x = 0; x < cw; x++) {
|
||||
const sx = Math.min(w - 1, Math.round((x / cw) * w));
|
||||
const sy = Math.min(h - 1, Math.round((y / ch) * h));
|
||||
if (bitset[sy * w + sx]) continue; // slab pixel: keep photo
|
||||
const checker = ((Math.floor(x / cell) + Math.floor(y / cell)) % 2 === 0);
|
||||
const i = (y * cw + x) * 4;
|
||||
const g = checker ? 210 : 235;
|
||||
px[i] = g; px[i + 1] = g; px[i + 2] = g; px[i + 3] = 255;
|
||||
}
|
||||
}
|
||||
ctx.putImageData(imgData, 0, 0);
|
||||
}
|
||||
|
||||
let fileInput: HTMLInputElement;
|
||||
|
||||
function pickFiles(e: Event) {
|
||||
@@ -93,7 +143,13 @@
|
||||
|
||||
{:else if kind === 'mask'}
|
||||
<h2>3. Mask</h2>
|
||||
<p>Adjust sheet and sensitivity, then approve the mask.</p>
|
||||
<p>Adjust sheet and sensitivity, then approve the mask. Removed background shows as a gray checkerboard.</p>
|
||||
{#if cap.source && maskObjUrl}
|
||||
<div class="mask-preview">
|
||||
<canvas bind:this={maskCanvas} aria-label="mask preview"></canvas>
|
||||
<img bind:this={maskImg} src={maskObjUrl} alt="calibration photo (hidden, drives mask preview)" class="mask-src" />
|
||||
</div>
|
||||
{/if}
|
||||
{#if control}
|
||||
<div class="controls">
|
||||
<label>Sheet
|
||||
@@ -154,6 +210,9 @@
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.mask-preview { margin: 1rem 0; }
|
||||
.mask-preview canvas { width: 100%; height: auto; max-height: 70vh; object-fit: contain; display: block; border: 1px solid #444; background: #000; }
|
||||
.mask-src { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
|
||||
.picker-row { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; margin: 1rem 0; color: #aaa; }
|
||||
.picker { min-height: 44px; padding: 0.65rem 1rem; background: #00e5ff; color: #002; border: 0; font-weight: 700; border-radius: 4px; }
|
||||
.file-input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
|
||||
|
||||
Reference in New Issue
Block a user