From 640715282c1a55ad388373231edc02af1ca2b4f0 Mon Sep 17 00:00:00 2001 From: Jules Date: Sun, 21 Jun 2026 23:32:50 +0200 Subject: [PATCH] Fix impression pellicule : la feuille -div2 doit etre envoyee a taille pleine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La PageSize CUPS w288h432-div2 declare la meme taille de page que w288h432 (confirme par le PPD Mitsubishi) : le K60 coupe la feuille pleine apres impression, il ne duplique pas une image fournie a taille reduite. On envoyait la bande seule (600x1800) sous cette PageSize, donc CUPS l'etirait pour remplir la feuille 1200x1800 puis le K60 la coupait en 2 — d'ou la photo etiree et coupee au lieu de 2 bandes identiques. Ajoute aussi le miroir horizontal sur le preview live (selfie) et un flou de chargement pendant le bref delai de remontee du miroir DSLR apres capture, pour eviter l'impression d'image figee sur la photo precedente. --- backend/printer.py | 37 ++++++++++++++++++++++++++++++++----- frontend/css/style.css | 5 +++++ frontend/index.html | 4 ++-- frontend/js/camera.js | 8 +++++++- 4 files changed, 46 insertions(+), 8 deletions(-) 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);