diff --git a/frontend/src/capture/SessionCapture.svelte b/frontend/src/capture/SessionCapture.svelte index 7f8ae58..9e9a661 100644 --- a/frontend/src/capture/SessionCapture.svelte +++ b/frontend/src/capture/SessionCapture.svelte @@ -68,6 +68,70 @@ ctx.putImageData(imgData, 0, 0); } + // Axis preview: the sheet-removed photo with the length-axis tape line + // (red) drawn from axis.tapeLine.start to .end in source-pixel coords. + let axisCanvas: HTMLCanvasElement; + let axisImg: HTMLImageElement; + let axisObjUrl = ''; + + $: if (kind === 'axis' && cap.source) { + if (axisObjUrl) URL.revokeObjectURL(axisObjUrl); + axisObjUrl = URL.createObjectURL(cap.source.blob); + } + + $: if (kind === 'axis' && axisImg?.complete && axisImg.naturalWidth > 0) { + drawAxisPreview(); + } + + function drawAxisPreview() { + const canvas = axisCanvas; + if (!canvas || !axisImg || !cap.approvedMask) return; + const w = axisImg.naturalWidth; + const h = axisImg.naturalHeight; + 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(axisImg, 0, 0, canvas.width, canvas.height); + // Checkerboard the removed (non-slab) pixels. + const bitset = cap.approvedMask.bitset; + const cw = canvas.width; + const ch = canvas.height; + const cell = 6; + 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; + 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); + // Draw the tape line (length axis) in red. + const [x1, y1] = cap.axis.tapeLine.start; + const [x2, y2] = cap.axis.tapeLine.end; + ctx.strokeStyle = '#ff3b30'; + ctx.lineWidth = Math.max(2, canvas.width / 300); + ctx.beginPath(); + ctx.moveTo((x1 / w) * cw, (y1 / h) * ch); + ctx.lineTo((x2 / w) * cw, (y2 / h) * ch); + ctx.stroke(); + // Endpoint dots + ctx.fillStyle = '#ff3b30'; + for (const [ex, ey] of [[x1, y1], [x2, y2]] as [number, number][]) { + ctx.beginPath(); + ctx.arc((ex / w) * cw, (ey / h) * ch, Math.max(3, canvas.width / 150), 0, Math.PI * 2); + ctx.fill(); + } + } + let fileInput: HTMLInputElement; // Auto-run the mask so the overlay (and preview) populate without the owner @@ -192,7 +256,13 @@ {:else if kind === 'axis'}
Place the tape endpoints at the two ends of the slab's length.
+The sheet is removed. The red line marks the slab's length axis — confirm it runs end-to-end, then approve.
+ {#if cap.source} +