diff --git a/backend/collage.py b/backend/collage.py index 1b53d52..09a5b06 100644 --- a/backend/collage.py +++ b/backend/collage.py @@ -10,52 +10,118 @@ log = logging.getLogger("photobooth.collage") # Format 10x15cm a 300dpi LARGEUR_10x15 = 1200 HAUTEUR_10x15 = 1800 +# Format 10x15cm paysage +LARGEUR_10x15_L = 1800 +HAUTEUR_10x15_L = 1200 def _crop_center(img: Image.Image, ratio_cible: float) -> Image.Image: """Recadre une image au centre pour correspondre au ratio cible.""" ratio_img = img.width / img.height if ratio_img > ratio_cible: - # Trop large, couper les cotes nouvelle_largeur = int(img.height * ratio_cible) offset = (img.width - nouvelle_largeur) // 2 return img.crop((offset, 0, offset + nouvelle_largeur, img.height)) else: - # Trop haut, couper haut/bas nouvelle_hauteur = int(img.width / ratio_cible) offset = (img.height - nouvelle_hauteur) // 2 return img.crop((0, offset, img.width, offset + nouvelle_hauteur)) -def creer_strip(photos: list[Path], marge: int = 30, couleur_fond: str = "#ffffff") -> Path: - """Cree une bande verticale (strip) au format 10x15cm.""" +def creer_strip(photos: list[Path], marge: int = 20, couleur_fond: str = "#ffffff") -> Path: + """Cree une bande portrait style pellicule (4 photos de gauche a droite). + + La bande fait la moitie d'un 10x15 paysage (1800 x 570px environ). + Style pellicule : bords avec perforations. + """ nb = len(photos) images = [Image.open(p) for p in photos] - # Calculer la taille de chaque cellule dans le format 10x15 - espace_disponible_h = HAUTEUR_10x15 - marge * (nb + 1) - hauteur_cellule = espace_disponible_h // nb - largeur_cellule = LARGEUR_10x15 - marge * 2 + # Dimensions de la bande : largeur d'un 10x15 paysage, hauteur = moitie + bande_largeur = LARGEUR_10x15_L + bande_hauteur = (HAUTEUR_10x15_L - marge * 3) // 2 # 2 bandes par page + + # Zone perforations pellicule (haut et bas) + perf_hauteur = 30 + zone_photos_hauteur = bande_hauteur - perf_hauteur * 2 + + # Taille de chaque photo dans la bande + espace_photos = bande_largeur - marge * (nb + 1) + largeur_cellule = espace_photos // nb + hauteur_cellule = zone_photos_hauteur ratio_cellule = largeur_cellule / hauteur_cellule - # Recadrer et redimensionner chaque photo + # Recadrer et redimensionner images_redim = [] for img in images: img_crop = _crop_center(img, ratio_cellule) img_redim = img_crop.resize((largeur_cellule, hauteur_cellule), Image.LANCZOS) images_redim.append(img_redim) - # Assembler sur le canvas 10x15 - strip = Image.new("RGB", (LARGEUR_10x15, HAUTEUR_10x15), couleur_fond) - y = marge + # Creer la bande + strip = Image.new("RGB", (bande_largeur, bande_hauteur), couleur_fond) + draw = ImageDraw.Draw(strip) + + # Perforations pellicule (haut) + for px in range(marge, bande_largeur - marge, 40): + draw.rounded_rectangle( + [px, 6, px + 20, perf_hauteur - 6], + radius=4, fill=(30, 30, 30) + ) + # Perforations pellicule (bas) + for px in range(marge, bande_largeur - marge, 40): + draw.rounded_rectangle( + [px, bande_hauteur - perf_hauteur + 6, px + 20, bande_hauteur - 6], + radius=4, fill=(30, 30, 30) + ) + + # Bords pellicule + draw.rectangle([0, 0, bande_largeur - 1, perf_hauteur], fill=(40, 40, 40)) + draw.rectangle([0, bande_hauteur - perf_hauteur, bande_largeur - 1, bande_hauteur - 1], + fill=(40, 40, 40)) + + # Re-dessiner les perforations par-dessus les bords + for px in range(marge, bande_largeur - marge, 40): + draw.rounded_rectangle([px, 6, px + 20, perf_hauteur - 6], radius=4, fill=couleur_fond) + draw.rounded_rectangle( + [px, bande_hauteur - perf_hauteur + 6, px + 20, bande_hauteur - 6], + radius=4, fill=couleur_fond + ) + + # Coller les photos + x = marge for img in images_redim: - strip.paste(img, (marge, y)) - y += hauteur_cellule + marge + strip.paste(img, (x, perf_hauteur)) + x += largeur_cellule + marge nom = f"strip_{photos[0].stem}.jpg" chemin = DOSSIER_EXPORTS / nom strip.save(chemin, "JPEG", quality=95, dpi=(300, 300)) - log.info(f"Strip 10x15 cree : {chemin} ({nb} photos)") + log.info(f"Strip pellicule cree : {chemin} ({nb} photos)") + return chemin + + +def creer_impression_strip(chemin_strip: Path, marge: int = 20, + couleur_fond: str = "#ffffff") -> Path: + """Cree un 10x15 paysage avec 2 bandes identiques superposees pour impression.""" + strip = Image.open(chemin_strip) + + # Canvas 10x15 paysage + page = Image.new("RGB", (LARGEUR_10x15_L, HAUTEUR_10x15_L), couleur_fond) + + # Redimensionner la bande pour qu'elle tienne dans la moitie + bande_largeur = LARGEUR_10x15_L - marge * 2 + bande_hauteur = (HAUTEUR_10x15_L - marge * 3) // 2 + strip_redim = strip.resize((bande_largeur, bande_hauteur), Image.LANCZOS) + + # Coller 2 fois + page.paste(strip_redim, (marge, marge)) + page.paste(strip_redim, (marge, marge + bande_hauteur + marge)) + + nom = f"print_{chemin_strip.stem}.jpg" + chemin = DOSSIER_EXPORTS / nom + page.save(chemin, "JPEG", quality=95, dpi=(300, 300)) + log.info(f"Page impression 10x15 creee : {chemin}") return chemin @@ -67,19 +133,16 @@ def creer_collage(photos: list[Path], colonnes: int = 2, marge: int = 25, lignes = (nb + colonnes - 1) // colonnes - # Calculer la taille de chaque cellule largeur_cellule = (LARGEUR_10x15 - marge * (colonnes + 1)) // colonnes hauteur_cellule = (HAUTEUR_10x15 - marge * (lignes + 1)) // lignes ratio_cellule = largeur_cellule / hauteur_cellule - # Recadrer et redimensionner images_redim = [] for img in images: img_crop = _crop_center(img, ratio_cellule) img_redim = img_crop.resize((largeur_cellule, hauteur_cellule), Image.LANCZOS) images_redim.append(img_redim) - # Assembler sur le canvas 10x15 collage = Image.new("RGB", (LARGEUR_10x15, HAUTEUR_10x15), couleur_fond) for i, img in enumerate(images_redim): diff --git a/backend/main.py b/backend/main.py index 9711949..0aa4327 100644 --- a/backend/main.py +++ b/backend/main.py @@ -16,7 +16,7 @@ from backend.config import ( from backend.camera import camera from backend.gallery import lister_photos, compter_photos, supprimer_photo, vider_galerie from backend.effects import appliquer_filtre, appliquer_overlay, chroma_key, lister_overlays, lister_fonds, FILTRES -from backend.collage import creer_strip, creer_collage +from backend.collage import creer_strip, creer_collage, creer_impression_strip from backend.destinations import distribuer_photo, detecter_usb, compteur_restant, reset_compteur from backend.printer import lister_imprimantes, imprimer from backend.mailer import envoyer_photo @@ -192,8 +192,15 @@ async def api_strip(donnees: dict): for c in chemins: if not c.exists(): return JSONResponse({"erreur": f"Photo introuvable : {c.name}"}, status_code=404) - chemin = creer_strip(chemins) - return {"nom": chemin.name, "chemin": f"/data/exports/{chemin.name}"} + chemin_strip = creer_strip(chemins) + # Creer aussi la page d'impression (2 bandes sur 10x15 paysage) + chemin_print = creer_impression_strip(chemin_strip) + return { + "nom": chemin_strip.name, + "chemin": f"/data/exports/{chemin_strip.name}", + "impression": chemin_print.name, + "chemin_impression": f"/data/exports/{chemin_print.name}", + } @app.post("/api/collage") diff --git a/frontend/js/camera.js b/frontend/js/camera.js index ee713dd..0ad6859 100644 --- a/frontend/js/camera.js +++ b/frontend/js/camera.js @@ -72,7 +72,7 @@ async function lancerCapture() { function getNombrePhotos() { if (modeActuel === 'simple') return 1; - if (modeActuel === 'multi') return config.multi_shot?.nombre_photos || 3; + if (modeActuel === 'multi') return config.multi_shot?.nombre_photos || 4; return 1; } @@ -172,17 +172,24 @@ function afficherFlash() { setTimeout(() => flash.classList.add('cache'), 400); } +let photoImpression = null; // Nom du fichier d'impression (2 bandes sur 10x15) + async function traiterCapture() { if (photosSession.length === 0) { allerA('accueil'); return; } + photoImpression = null; + if (modeActuel === 'multi') { const mode = config.multi_shot?.mode || 'strip'; let resultat; if (mode === 'strip') { resultat = await apiPost('/api/strip', { photos: photosSession }); + if (resultat.impression) { + photoImpression = resultat.impression; + } } else { resultat = await apiPost('/api/collage', { photos: photosSession }); } diff --git a/frontend/js/share.js b/frontend/js/share.js index 2a487a5..ce20628 100644 --- a/frontend/js/share.js +++ b/frontend/js/share.js @@ -22,8 +22,10 @@ function changerExemplaires(delta) { async function lancerImpression() { if (!photoFinale) return; fermerImpression(); + // Pour les strips, imprimer la version 2 bandes sur 10x15 + const fichierImpression = photoImpression || photoFinale; afficherStatut(`Impression de ${nbExemplaires} exemplaire(s)...`, 'succes'); - const resultat = await apiPost('/api/imprimer', { photo: photoFinale, copies: nbExemplaires }); + const resultat = await apiPost('/api/imprimer', { photo: fichierImpression, copies: nbExemplaires }); if (resultat.succes) { afficherStatut(`${nbExemplaires} exemplaire(s) envoye(s) a l'imprimante !`, 'succes'); } else {