diff --git a/backend/printer.py b/backend/printer.py index e30bb03..806ed18 100644 --- a/backend/printer.py +++ b/backend/printer.py @@ -15,8 +15,13 @@ FORMATS = { "10x15": {"cups": "w288h432", "px": (1200, 1800)}, "15x15": {"cups": "w432h432", "px": (1800, 1800)}, "15x20": {"cups": "w432h576", "px": (1800, 2400)}, - "10x15-2up": {"cups": "w288h432-div2", "px": (600, 1800)}, # 1 strip 5x15cm, printer duplique - "15x20-2up": {"cups": "w432h576-div2", "px": (900, 2400)}, # 1 strip 7.5x20cm, printer duplique + # "-div2" declare en CUPS la MEME taille de page que le format plein (le PPD le confirme : + # PaperDimension identique pour w288h432 et w288h432-div2). Le K60 ne duplique pas une + # image fournie a taille reduite : il imprime la feuille entiere telle qu'envoyee puis la + # coupe en 2 apres impression. "px" = taille d'une bande seule, "px_page" = feuille entiere + # a envoyer a lp (bande dupliquee cote a cote par _dupliquer_page). + "10x15-2up": {"cups": "w288h432-div2", "px": (600, 1800), "px_page": (1200, 1800)}, + "15x20-2up": {"cups": "w432h576-div2", "px": (900, 2400), "px_page": (1800, 2400)}, } ERREUR_FICHIER = "fichier_introuvable" @@ -104,6 +109,22 @@ def _preparer_image(chemin: Path, largeur: int, hauteur: int, return Path(tmp.name) +def _dupliquer_page(chemin: Path, taille_page: tuple[int, int]) -> Path: + """Colle la bande 2 fois cote a cote pour remplir la feuille entiere. + + Necessaire pour les formats "-div2" : le K60 coupe la feuille imprimee en 2 + apres impression, il ne duplique pas une image fournie a taille reduite. + """ + from PIL import Image + img = Image.open(chemin) + page = Image.new("RGB", taille_page, (255, 255, 255)) + page.paste(img, (0, 0)) + page.paste(img, (img.width, 0)) + tmp = tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) + page.save(tmp.name, "JPEG", quality=95, dpi=(300, 300)) + return Path(tmp.name) + + def _statut(nom: str) -> str: """Retourne 'idle', 'printing', 'stopped' ou 'inconnue'.""" code, out, _ = _run(["lpstat", "-p", nom]) @@ -205,7 +226,13 @@ def imprimer( return {"succes": False, "erreur": ERREUR_FICHIER, "message": "Fichier photo introuvable"} chemin_print = _preparer_image(chemin_photo, largeur, hauteur, fmt_base, cadre, orientation) - tmp_cree = chemin_print != chemin_photo + tmp_a_supprimer = [chemin_print] if chemin_print != chemin_photo else [] + + px_page = fmt.get("px_page") + if px_page: + chemin_page = _dupliquer_page(chemin_print, px_page) + tmp_a_supprimer.append(chemin_page) + chemin_print = chemin_page if _statut(imprimante) == "stopped": log.warning(f"Imprimante {imprimante} arrêtée — réactivation") @@ -261,5 +288,5 @@ def imprimer( "message": "Impression échouée après 3 tentatives", } finally: - if tmp_cree: - chemin_print.unlink(missing_ok=True) + for tmp in tmp_a_supprimer: + tmp.unlink(missing_ok=True) diff --git a/frontend/css/style.css b/frontend/css/style.css index 7e4823f..5a899cc 100644 --- a/frontend/css/style.css +++ b/frontend/css/style.css @@ -325,6 +325,11 @@ html, body { width: 100%; height: 100%; object-fit: contain; + transform: scaleX(-1); /* miroir selfie : ce que voit l'utilisateur en se posant */ +} + +.preview-live img.preview-chargement { + filter: blur(8px) brightness(0.6); } .cadre-guide-recadrage { diff --git a/frontend/index.html b/frontend/index.html index 65bb2e7..85ddf6e 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -7,7 +7,7 @@ - + @@ -739,7 +739,7 @@ - + diff --git a/frontend/js/camera.js b/frontend/js/camera.js index 2d67b1f..71e7ab5 100644 --- a/frontend/js/camera.js +++ b/frontend/js/camera.js @@ -48,6 +48,9 @@ function lancerPreview() { if (previewActif) return; previewActif = true; afficherErreurPreview(false); + // Flou le temps que la 1ere frame fraiche arrive (le miroir DSLR doit remonter + // apres la capture precedente) : evite l'impression d'image figee sur l'ancienne photo. + document.getElementById('img-preview')?.classList.add('preview-chargement'); wsEnvoyer({ type: 'preview_start' }); resetPreviewTimeout(); } @@ -72,7 +75,10 @@ wsOnMessage('preview', (msg) => { if (!previewActif) return; const img = document.getElementById('img-preview'); if (img) { - img.onload = positionnerGuideRecadrage; + img.onload = () => { + img.classList.remove('preview-chargement'); + positionnerGuideRecadrage(); + }; img.src = msg.image; } afficherErreurPreview(false);