diff --git a/backend/camera.py b/backend/camera.py index 6aa8f8e..063740c 100644 --- a/backend/camera.py +++ b/backend/camera.py @@ -172,6 +172,7 @@ class Camera: tmp_path = str(chemin_dest) + ".tmp" fichier_camera.save(tmp_path) self.activer_viewfinder() # Miroir relevé immédiatement après la prise + self._chauffer_liveview() # Absorbe la stabilisation LiveView ici (spinner visible), pas sur le countdown suivant from PIL import ImageOps as PILImageOps img = PILImageOps.exif_transpose(PILImage.open(tmp_path)) if img.width > 4000: @@ -189,6 +190,19 @@ class Camera: log.error(f"Erreur capture DSLR : {e}") return None + def _chauffer_liveview(self, tentatives: int = 8, pause: float = 0.15): + """Consomme les premieres frames LiveView juste apres la remontee du miroir + (souvent en erreur ~1-2s le temps que l'AF/capteur se stabilise), pendant + que le spinner de traitement est affiche — pour que le preview du prochain + countdown soit deja a jour au lieu de rester figé sur l'ancienne photo.""" + for _ in range(tentatives): + try: + with self._gp_lock: + self.camera.capture_preview() + return + except Exception: + time.sleep(pause) + def _capture_webcam(self, chemin_dest: Path) -> Path | None: if not self.webcam or not self.webcam.isOpened(): return None diff --git a/backend/collage.py b/backend/collage.py index 3455fe0..af4c1bb 100644 --- a/backend/collage.py +++ b/backend/collage.py @@ -47,8 +47,13 @@ def creer_strip(photos: list[Path], marge: int = 10, couleur_fond: str = "#00000 bande_largeur = 600 bande_hauteur = 1800 - # Zone perforations pellicule (gauche et droite) - perf_largeur = 34 + # Zone perforations pellicule (gauche et droite). Cette zone noire est aussi + # la marge de securite pour la coupe physique du K60 : la feuille pleine + # (2 bandes cote a cote) est coupee au centre apres impression, et la coupe + # n'est jamais parfaitement precise. Une marge trop fine fait que la coupe + # tombe dans la photo d'un cote (pellicule absente d'un cote, visible de + # l'autre). Elargie a 55px (~4.6mm) pour absorber le jeu mecanique du cutter. + perf_largeur = 55 zone_photos_largeur = bande_largeur - perf_largeur * 2 # Taille de chaque photo dans la bande (separees par bandes noires) @@ -70,16 +75,19 @@ def creer_strip(photos: list[Path], marge: int = 10, couleur_fond: str = "#00000 draw = ImageDraw.Draw(strip) # Bords pellicule noirs (deja le fond) - # Perforations pellicule (gauche) - trous blancs dans le bord noir + # Perforations pellicule (gauche) - trous gris dans le bord noir, centrees + # dans la marge (laisse du noir plein des 2 cotes pour la marge de coupe) + trou_largeur = 26 + trou_x = (perf_largeur - trou_largeur) // 2 for py in range(marge + 10, bande_hauteur - marge, 44): draw.rounded_rectangle( - [8, py, perf_largeur - 8, py + 22], + [trou_x, py, trou_x + trou_largeur, py + 22], radius=5, fill=(60, 60, 60) ) # Perforations pellicule (droite) for py in range(marge + 10, bande_hauteur - marge, 44): draw.rounded_rectangle( - [bande_largeur - perf_largeur + 8, py, bande_largeur - 8, py + 22], + [bande_largeur - perf_largeur + trou_x, py, bande_largeur - perf_largeur + trou_x + trou_largeur, py + 22], radius=5, fill=(60, 60, 60) ) diff --git a/frontend/index.html b/frontend/index.html index 85ddf6e..8851f7f 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -739,7 +739,7 @@ - + diff --git a/frontend/js/camera.js b/frontend/js/camera.js index 71e7ab5..90a3f2d 100644 --- a/frontend/js/camera.js +++ b/frontend/js/camera.js @@ -94,7 +94,7 @@ function getRatioCelluleAttendu() { const mode = config.multi_shot?.mode || 'strip'; if (mode === 'strip') { // Reproduit creer_strip() dans backend/collage.py (bande portrait 600x1800, photos empilees) - const bandeLargeur = 600, bandeHauteur = 1800, marge = 10, sep = 12, perfLargeur = 34; + const bandeLargeur = 600, bandeHauteur = 1800, marge = 10, sep = 12, perfLargeur = 55; const zonePhotosLargeur = bandeLargeur - perfLargeur * 2; const espacePhotos = bandeHauteur - marge * 2 - sep * (nb - 1); const hauteurCellule = Math.floor(espacePhotos / nb);