import { minAreaRect, slabBounds } from '../geometry/rect'; import type { MaskParams, WorkerIn, WorkerOut } from '../capture/capture.types'; import { decodeOrientedBitmap } from '../decodeOrientedBitmap'; import { greenKeep, blackKeep, autoPickSheet } from '../mask/sheetKeep'; let bitmap: ImageBitmap | null = null; let data: Uint8ClampedArray | null = null; let w = 0, h = 0; let current = 0; // highest job id issued by main; older replies are dropped function runMask(params: MaskParams, id: number): WorkerOut { if (!data) return { id, kind: 'failed', reason: 'no source decoded' }; let sheet = params.sheet; if (sheet === 'auto') { const samples: { r: number; g: number; b: number }[] = []; for (let y = 0; y < h; y += 16) for (let x = 0; x < w; x += 16) { const i = (y * w + x) * 4; samples.push({ r: data[i], g: data[i + 1], b: data[i + 2] }); } sheet = autoPickSheet(samples); } const keep = sheet === 'green' ? greenKeep : blackKeep; const s = Math.max(0, Math.min(100, params.sensitivity)); const bitset = new Uint8Array(w * h); let slabPx = 0; for (let i = 0, p = 0; i < data.length; i += 4, p++) { if (keep(data[i], data[i + 1], data[i + 2], s)) { bitset[p] = 1; slabPx++; } } if (slabPx === 0) { return { id, kind: 'ready', slabPx: 0, bitset, bbox: { corners: [], longSidePx: 0, angleDeg: 0 }, slabBounds: { x: 0, y: 0, w: 0, h: 0 } }; } const bbox = minAreaRect(bitset, w, h); if (!bbox) { return { id, kind: 'ready', slabPx: 0, bitset, bbox: { corners: [], longSidePx: 0, angleDeg: 0 }, slabBounds: { x: 0, y: 0, w: 0, h: 0 } }; } return { id, kind: 'ready', slabPx, bitset, bbox, slabBounds: slabBounds(bitset, w, h) }; } self.onmessage = (e: MessageEvent) => { const msg = e.data; if (msg.id < current) return; // superseded by a newer job current = msg.id; if (msg.kind === 'init') { if (bitmap) { bitmap.close(); bitmap = null; } data = null; w = h = 0; decodeOrientedBitmap(msg.blob) .then((bmp) => { w = bmp.width; h = bmp.height; const canvas = new OffscreenCanvas(w, h); const ctx = canvas.getContext('2d')!; ctx.drawImage(bmp, 0, 0); data = ctx.getImageData(0, 0, w, h).data; bitmap = bmp; self.postMessage({ id: msg.id, kind: 'decoded', w, h } as WorkerOut); }) .catch((err) => self.postMessage({ id: msg.id, kind: 'failed', reason: String(err) } satisfies WorkerOut)); return; } if (msg.kind === 'invalidate') { if (bitmap) { bitmap.close(); bitmap = null; } data = null; w = h = 0; return; } try { const out = runMask({ sheet: msg.sheet, sensitivity: msg.sensitivity }, msg.id); if (out.kind === 'ready') { self.postMessage(out, [out.bitset.buffer]); } else { self.postMessage(out); } } catch (err) { self.postMessage({ id: msg.id, kind: 'failed', reason: String(err) } satisfies WorkerOut); } }; export {};