import { forwardRef, useEffect, useImperativeHandle, useRef } from "react"; import type { Quad } from "./useEdgeDetection.js"; const MAX_DISPLAY_WIDTH = 1600; export interface CropCanvasHandle { /** Returns a canvas with the clean image (no overlay) for use with cv.imread. */ getCleanCanvas(): HTMLCanvasElement | null; } interface Props { imageUrl: string; corners: Quad; onReady?: (canvas: HTMLCanvasElement, width: number, height: number) => void; } function drawOverlay(ctx: CanvasRenderingContext2D, width: number, height: number, corners: Quad) { let [tl, tr, br, bl] = corners; ctx.save(); // Dark overlay with polygon cutout (even-odd fill rule creates a "window") ctx.fillStyle = "rgba(0, 0, 0, 0.5)"; ctx.beginPath(); ctx.rect(0, 0, width, height); ctx.moveTo(tl.x, tl.y); ctx.lineTo(tr.x, tr.y); ctx.lineTo(br.x, br.y); ctx.lineTo(bl.x, bl.y); ctx.closePath(); ctx.fill("evenodd"); // Selection border ctx.strokeStyle = "#2196f3"; ctx.lineWidth = Math.max(2, width * 0.003); ctx.beginPath(); ctx.moveTo(tl.x, tl.y); ctx.lineTo(tr.x, tr.y); ctx.lineTo(br.x, br.y); ctx.lineTo(bl.x, bl.y); ctx.closePath(); ctx.stroke(); ctx.restore(); } /** * Renders the image on a canvas with a perspective-crop overlay. * Exposes `getImageData()` so callers can extract pixels for off-thread processing. */ export let CropCanvas = forwardRef(function CropCanvas({ imageUrl, corners, onReady }, ref) { let canvasRef = useRef(null); let imageRef = useRef(null); useEffect(() => { let img = new Image(); img.onload = () => { imageRef.current = img; let canvas = canvasRef.current; if (!canvas) return; let scale = Math.min(1, MAX_DISPLAY_WIDTH / img.naturalWidth); canvas.width = Math.round(img.naturalWidth * scale); canvas.height = Math.round(img.naturalHeight * scale); let ctx = canvas.getContext("2d"); ctx?.drawImage(img, 0, 0, canvas.width, canvas.height); drawOverlay(ctx!, canvas.width, canvas.height, corners); onReady?.(canvas, canvas.width, canvas.height); }; img.src = imageUrl; }, [imageUrl]); // eslint-disable-line react-hooks/exhaustive-deps // Redraw overlay whenever corners change useEffect(() => { let canvas = canvasRef.current; let img = imageRef.current; if (!canvas || !img) return; let ctx = canvas.getContext("2d"); if (!ctx) return; ctx.drawImage(img, 0, 0, canvas.width, canvas.height); drawOverlay(ctx, canvas.width, canvas.height, corners); }, [corners]); useImperativeHandle(ref, () => ({ getCleanCanvas(): HTMLCanvasElement | null { let canvas = canvasRef.current; let img = imageRef.current; if (!canvas || !img) return null; let clean = document.createElement("canvas"); clean.width = canvas.width; clean.height = canvas.height; clean.getContext("2d")?.drawImage(img, 0, 0, canvas.width, canvas.height); return clean; }, })); return ; });