diff --git a/js/v2/inscr-gestion.js b/js/v2/inscr-gestion.js index be2379e..7175344 100644 --- a/js/v2/inscr-gestion.js +++ b/js/v2/inscr-gestion.js @@ -492,10 +492,12 @@ return Tesseract.createWorker('eng'); }).then(function (worker) { globalOcrWorker = worker; - // Chiffres seulement. Pas de SINGLE_LINE : trop fragile sur manuscrit espace. - return worker.setParameters({ - tessedit_char_whitelist: '0123456789' - }).then(function () { + // Chiffres seulement + bloc (dossard type "1020", pas une ligne unique fragile). + var params = {tessedit_char_whitelist: '0123456789'}; + if (window.Tesseract && Tesseract.PSM && Tesseract.PSM.SINGLE_BLOCK) { + params.tessedit_pageseg_mode = Tesseract.PSM.SINGLE_BLOCK; + } + return worker.setParameters(params).then(function () { return globalOcrWorker; }); }).catch(function (err) { @@ -511,38 +513,129 @@ }); } - // Un numero = tous les chiffres lus dans le cadre (5+2+1 → 521). + // MSIN-4401 — Extraire UN numero de dossard : + // - manuscrit "5 2 1" → coller les chiffres isoles → 521 + // - dossard "1 KM" + "1020" → garder le plus long groupe → 1020 (pas 11020) function extractBibNumber(raw) { - var digits = String(raw || '').replace(/\D/g, ''); - if (digits.length < 1 || digits.length > BIB_MAX_LEN) { + var matches = String(raw || '').match(/\d+/g); + if (!matches || !matches.length) { return ''; } - return digits; + var i; + var allSingles = true; + for (i = 0; i < matches.length; i++) { + if (matches[i].length !== 1) { + allSingles = false; + break; + } + } + if (allSingles) { + var joined = matches.join(''); + return (joined.length >= 1 && joined.length <= BIB_MAX_LEN) ? joined : ''; + } + var best = ''; + for (i = 0; i < matches.length; i++) { + if (matches[i].length <= BIB_MAX_LEN && matches[i].length > best.length) { + best = matches[i]; + } + } + return best; } - // Crop central du flux (region du cadre blanc). - function captureCenterFrame(videoEl) { + // Zone video visible avec object-fit:cover (alignee sur le carre a l'ecran). + function getCoverVisibleRect(videoEl, displayEl) { + var vw = videoEl.videoWidth; + var vh = videoEl.videoHeight; + var dw = displayEl.clientWidth || 1; + var dh = displayEl.clientHeight || 1; + var videoRatio = vw / vh; + var displayRatio = dw / dh; + var renderedW; + var renderedH; + var offsetX; + var offsetY; + if (videoRatio > displayRatio) { + renderedH = vh; + renderedW = vh * displayRatio; + offsetX = (vw - renderedW) / 2; + offsetY = 0; + } else { + renderedW = vw; + renderedH = vw / displayRatio; + offsetX = 0; + offsetY = (vh - renderedH) / 2; + } + return { + offsetX: offsetX, + offsetY: offsetY, + renderedW: renderedW, + renderedH: renderedH + }; + } + + // Renforce le contraste pour l'OCR (dossard blanc sur fond photo). + function preprocessOcrCanvas(srcCanvas) { + var w = srcCanvas.width; + var h = srcCanvas.height; + var scale = (w < 280) ? 2 : 1; + var out = document.createElement('canvas'); + out.width = Math.max(1, Math.round(w * scale)); + out.height = Math.max(1, Math.round(h * scale)); + var ctx = out.getContext('2d'); + ctx.imageSmoothingEnabled = true; + ctx.drawImage(srcCanvas, 0, 0, out.width, out.height); + var img = ctx.getImageData(0, 0, out.width, out.height); + var d = img.data; + var j; + for (j = 0; j < d.length; j += 4) { + var gray = (0.299 * d[j] + 0.587 * d[j + 1] + 0.114 * d[j + 2]); + // Contraste fort : pousse vers noir/blanc (chiffres blancs sur fond sombre). + gray = (gray - 128) * 1.6 + 128; + if (gray < 0) { gray = 0; } + if (gray > 255) { gray = 255; } + d[j] = d[j + 1] = d[j + 2] = gray; + } + ctx.putImageData(img, 0, 0); + return out; + } + + // Crop = cadre blanc (~55% x 30%) sur la zone VISIBLE (cover), pas le flux brut. + function captureCenterFrame(videoEl, displayEl) { if (!videoEl || !videoEl.videoWidth) { return null; } - var w = videoEl.videoWidth; - var h = videoEl.videoHeight; - var cropW = Math.round(w * 0.55); - var cropH = Math.round(h * 0.3); - var sx = Math.round((w - cropW) / 2); - var sy = Math.round((h - cropH) / 2); + var cropRatioW = 0.55; + var cropRatioH = 0.3; + var sx; + var sy; + var cropW; + var cropH; + if (displayEl && displayEl.clientWidth && displayEl.clientHeight) { + var vis = getCoverVisibleRect(videoEl, displayEl); + cropW = Math.max(1, Math.round(vis.renderedW * cropRatioW)); + cropH = Math.max(1, Math.round(vis.renderedH * cropRatioH)); + sx = Math.round(vis.offsetX + (vis.renderedW - cropW) / 2); + sy = Math.round(vis.offsetY + (vis.renderedH - cropH) / 2); + } else { + var w = videoEl.videoWidth; + var h = videoEl.videoHeight; + cropW = Math.round(w * cropRatioW); + cropH = Math.round(h * cropRatioH); + sx = Math.round((w - cropW) / 2); + sy = Math.round((h - cropH) / 2); + } var canvas = document.createElement('canvas'); var outW = cropW; var outH = cropH; if (cropW > OCR_MAX_WIDTH) { - var scale = OCR_MAX_WIDTH / cropW; + var scaleDown = OCR_MAX_WIDTH / cropW; outW = OCR_MAX_WIDTH; - outH = Math.max(1, Math.round(cropH * scale)); + outH = Math.max(1, Math.round(cropH * scaleDown)); } canvas.width = outW; canvas.height = outH; canvas.getContext('2d').drawImage(videoEl, sx, sy, cropW, cropH, 0, 0, outW, outH); - return canvas; + return preprocessOcrCanvas(canvas); } function BibScanSession(block) { @@ -720,7 +813,7 @@ if (self.ocrBusy || !self.video || activeScan !== self || !self.workerReady) { return; } - var canvas = captureCenterFrame(self.video); + var canvas = captureCenterFrame(self.video, self.captureEl); if (!canvas) { self.setStatus(scanMsg(self.panel, 'data-msg-camera', 'Impossible d\'accéder à la caméra.', 'Unable to access the camera.'), false); return; diff --git a/php/inc_settings.php b/php/inc_settings.php index d740d43..a7debfb 100644 --- a/php/inc_settings.php +++ b/php/inc_settings.php @@ -7,7 +7,7 @@ * Constantes * * **************/ -define('_VERSION_CODE', '4.72.795'); +define('_VERSION_CODE', '4.72.796'); define('_DATE_CODE', '2026-07-13'); //MSIN-4290 define('QR_SECRET_KEY', 'ms1_qr_2026_cle_secrete_longue_et_fixe');