75 lines
2.1 KiB
JavaScript
75 lines
2.1 KiB
JavaScript
|
|
/**
|
||
|
|
* Browser-side Tesseract OCR for Layer-1 card name strip recognition.
|
||
|
|
* Uses tesseract.js workers (off main thread). Language data loads from CDN on first use.
|
||
|
|
*/
|
||
|
|
|
||
|
|
let workerPromise = null;
|
||
|
|
|
||
|
|
async function getWorker() {
|
||
|
|
if (!workerPromise) {
|
||
|
|
workerPromise = (async () => {
|
||
|
|
const { createWorker } = await import('tesseract.js');
|
||
|
|
const worker = await createWorker('eng', 1, {
|
||
|
|
logger: () => {},
|
||
|
|
});
|
||
|
|
await worker.setParameters({
|
||
|
|
tessedit_pageseg_mode: '6',
|
||
|
|
});
|
||
|
|
return worker;
|
||
|
|
})();
|
||
|
|
}
|
||
|
|
return workerPromise;
|
||
|
|
}
|
||
|
|
|
||
|
|
function cropNameStrip(imageDataUrl) {
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
const img = new Image();
|
||
|
|
img.onload = () => {
|
||
|
|
const stripHeight = Math.max(1, Math.floor(img.height * 0.35));
|
||
|
|
const canvas = document.createElement('canvas');
|
||
|
|
canvas.width = img.width;
|
||
|
|
canvas.height = stripHeight;
|
||
|
|
const ctx = canvas.getContext('2d');
|
||
|
|
ctx.drawImage(img, 0, 0, img.width, stripHeight, 0, 0, img.width, stripHeight);
|
||
|
|
resolve(canvas.toDataURL('image/jpeg', 0.92));
|
||
|
|
};
|
||
|
|
img.onerror = () => reject(new Error('Failed to load image for OCR'));
|
||
|
|
img.src = imageDataUrl;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* OCR the top name strip of a card crop.
|
||
|
|
* @param {string} imageDataUrl - full card crop data URL
|
||
|
|
* @returns {Promise<{ text: string, confidence: number }>}
|
||
|
|
*/
|
||
|
|
export async function recognizeCardNameStrip(imageDataUrl) {
|
||
|
|
if (typeof window === 'undefined') {
|
||
|
|
return { text: '', confidence: 0 };
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
const stripUrl = await cropNameStrip(imageDataUrl);
|
||
|
|
const worker = await getWorker();
|
||
|
|
const { data } = await worker.recognize(stripUrl);
|
||
|
|
return {
|
||
|
|
text: (data.text || '').trim(),
|
||
|
|
confidence: Math.round(data.confidence || 0),
|
||
|
|
};
|
||
|
|
} catch (error) {
|
||
|
|
console.warn('[ocr-worker] recognizeCardNameStrip failed:', error);
|
||
|
|
return { text: '', confidence: 0 };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function terminateOcrWorker() {
|
||
|
|
if (!workerPromise) return;
|
||
|
|
try {
|
||
|
|
const worker = await workerPromise;
|
||
|
|
await worker.terminate();
|
||
|
|
} catch {
|
||
|
|
// ignore teardown errors
|
||
|
|
}
|
||
|
|
workerPromise = null;
|
||
|
|
}
|