diff --git a/frontend/css/style.css b/frontend/css/style.css index 99bafd3..7e4823f 100644 --- a/frontend/css/style.css +++ b/frontend/css/style.css @@ -327,6 +327,14 @@ html, body { object-fit: contain; } +.cadre-guide-recadrage { + position: absolute; + box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5); + border: 3px dashed rgba(255, 255, 255, 0.85); + pointer-events: none; + box-sizing: border-box; +} + .preview-erreur-camera { position: absolute; top: 0; left: 0; right: 0; bottom: 0; diff --git a/frontend/index.html b/frontend/index.html index db2150d..bd5ba05 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -87,6 +87,7 @@
Preview +
📷

Appareil photo deconnecte

@@ -738,7 +739,7 @@ - + diff --git a/frontend/js/camera.js b/frontend/js/camera.js index e7bc793..ba1dfe1 100644 --- a/frontend/js/camera.js +++ b/frontend/js/camera.js @@ -65,16 +65,91 @@ function arreterPreview() { previewTimeoutId = null; } afficherErreurPreview(false); + document.getElementById('cadre-guide-recadrage')?.classList.add('cache'); } wsOnMessage('preview', (msg) => { if (!previewActif) return; const img = document.getElementById('img-preview'); - if (img) img.src = msg.image; + if (img) { + img.onload = positionnerGuideRecadrage; + img.src = msg.image; + } afficherErreurPreview(false); resetPreviewTimeout(); }); +// --- Cadre de visee : montre en live la zone qui sera gardee apres recadrage --- +// (la pellicule/le collage recadrent chaque photo au centre selon un ratio different du plein cadre camera) + +function getRatioCelluleAttendu() { + if (modeActuel !== 'multi') return null; + const nb = getNombrePhotos(); + const mode = config.multi_shot?.mode || 'strip'; + if (mode === 'strip') { + // Reproduit creer_strip() dans backend/collage.py + const bandeLargeur = 1800, bandeHauteurTotale = 1200, marge = 10, sep = 12, perfHauteur = 34; + const bandeHauteur = Math.floor((bandeHauteurTotale - marge) / 2); + const zonePhotosHauteur = bandeHauteur - perfHauteur * 2; + const espacePhotos = bandeLargeur - marge * 2 - sep * (nb - 1); + const largeurCellule = Math.floor(espacePhotos / nb); + return largeurCellule / zonePhotosHauteur; + } else { + // Reproduit creer_collage() dans backend/collage.py + const colonnes = 2; + const largeurPage = 1200, hauteurPage = 1800, margeC = 25; + const lignes = Math.ceil(nb / colonnes); + const largeurCellule = Math.floor((largeurPage - margeC * (colonnes + 1)) / colonnes); + const hauteurCellule = Math.floor((hauteurPage - margeC * (lignes + 1)) / lignes); + return largeurCellule / hauteurCellule; + } +} + +function positionnerGuideRecadrage() { + const guide = document.getElementById('cadre-guide-recadrage'); + const img = document.getElementById('img-preview'); + const container = document.getElementById('preview-live'); + if (!guide || !img || !container) return; + + const ratioCible = getRatioCelluleAttendu(); + if (!ratioCible || !img.naturalWidth || !img.naturalHeight) { + guide.classList.add('cache'); + return; + } + + const cw = container.clientWidth, ch = container.clientHeight; + const ratioImg = img.naturalWidth / img.naturalHeight; + const ratioContainer = cw / ch; + + // Boite affichee de l'image dans le conteneur (object-fit: contain) + let imgW, imgH, imgTop, imgLeft; + if (ratioImg > ratioContainer) { + imgW = cw; imgH = cw / ratioImg; + imgLeft = 0; imgTop = (ch - imgH) / 2; + } else { + imgH = ch; imgW = ch * ratioImg; + imgTop = 0; imgLeft = (cw - imgW) / 2; + } + + // Boite du recadrage final, centree dans l'image affichee (meme logique que _crop_center cote backend) + let guideW, guideH; + if (ratioImg > ratioCible) { + guideH = imgH; + guideW = imgH * ratioCible; + } else { + guideW = imgW; + guideH = imgW / ratioCible; + } + + guide.style.left = `${imgLeft + (imgW - guideW) / 2}px`; + guide.style.top = `${imgTop + (imgH - guideH) / 2}px`; + guide.style.width = `${guideW}px`; + guide.style.height = `${guideH}px`; + guide.classList.remove('cache'); +} + +window.addEventListener('resize', positionnerGuideRecadrage); + // Erreur camera pendant preview idle : retour accueil // Si on est en cours de capture (compte à rebours ou attente API), on ignore : // l'API /api/capturer retournera elle-même une erreur si le DSLR est vraiment mort.