import logging from pathlib import Path from PIL import Image, ImageDraw from backend.config import DOSSIER_EXPORTS, charger_config from backend.effects import appliquer_cadre_impression 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: nouvelle_largeur = int(img.height * ratio_cible) offset = (img.width - nouvelle_largeur) // 2 return img.crop((offset, 0, offset + nouvelle_largeur, img.height)) else: 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 = 10, couleur_fond: str = "#000000", cadre: str | None = None) -> Path: """Cree une bande portrait style pellicule (photos empilees de haut en bas). Pellicule noire avec perforations sur les cotes, photos separees par des bandes noires. Taille fixe 600x1800px (5x15cm a 300dpi) : c'est le format attendu par l'imprimante en mode "10x15-2up" (PageSize ...-div2), qui duplique et decoupe automatiquement la bande en 2 exemplaires sur une feuille 10x15. Doit rester strictement portrait (largeur < hauteur) et a cette taille exacte pour eviter toute rotation/etirement a l'impression. """ nb = len(photos) images = [Image.open(p) for p in photos] # Dimensions de la bande : format fixe attendu par printer.py (FORMATS["10x15-2up"]) bande_largeur = 600 bande_hauteur = 1800 # Zone perforations pellicule (gauche et droite) perf_largeur = 34 zone_photos_largeur = bande_largeur - perf_largeur * 2 # Taille de chaque photo dans la bande (separees par bandes noires) sep = 12 # separateur noir entre photos espace_photos = bande_hauteur - marge * 2 - sep * (nb - 1) hauteur_cellule = espace_photos // nb largeur_cellule = zone_photos_largeur 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) # Creer la bande (fond noir = pellicule) strip = Image.new("RGB", (bande_largeur, bande_hauteur), (20, 20, 20)) draw = ImageDraw.Draw(strip) # Bords pellicule noirs (deja le fond) # Perforations pellicule (gauche) - trous blancs dans le bord noir for py in range(marge + 10, bande_hauteur - marge, 44): draw.rounded_rectangle( [8, py, perf_largeur - 8, 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], radius=5, fill=(60, 60, 60) ) # Coller les photos (separees par des bandes noires horizontales) y = marge for img in images_redim: strip.paste(img, (perf_largeur, y)) y += hauteur_cellule + sep if cadre: strip = appliquer_cadre_impression(strip, "strip", cadre) nom = f"strip_{photos[0].stem}.jpg" chemin = DOSSIER_EXPORTS / nom strip.save(chemin, "JPEG", quality=95, dpi=(300, 300)) 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 def creer_collage(photos: list[Path], colonnes: int = 2, marge: int = 25, couleur_fond: str = "#ffffff", cadre: str | None = None) -> Path: """Cree un collage en grille au format 10x15cm.""" nb = len(photos) images = [Image.open(p) for p in photos] lignes = (nb + colonnes - 1) // colonnes largeur_cellule = (LARGEUR_10x15 - marge * (colonnes + 1)) // colonnes hauteur_cellule = (HAUTEUR_10x15 - marge * (lignes + 1)) // lignes ratio_cellule = largeur_cellule / hauteur_cellule 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) collage = Image.new("RGB", (LARGEUR_10x15, HAUTEUR_10x15), couleur_fond) for i, img in enumerate(images_redim): col = i % colonnes lig = i // colonnes x = marge + col * (largeur_cellule + marge) y = marge + lig * (hauteur_cellule + marge) collage.paste(img, (x, y)) if cadre: collage = appliquer_cadre_impression(collage, "10x15", cadre) nom = f"collage_{photos[0].stem}.jpg" chemin = DOSSIER_EXPORTS / nom collage.save(chemin, "JPEG", quality=95, dpi=(300, 300)) log.info(f"Collage 10x15 cree : {chemin} ({nb} photos, {colonnes}x{lignes})") return chemin