diff --git a/backend/collage.py b/backend/collage.py index 8fcf757..1b53d52 100644 --- a/backend/collage.py +++ b/backend/collage.py @@ -7,55 +7,80 @@ from backend.config import DOSSIER_EXPORTS, charger_config log = logging.getLogger("photobooth.collage") +# Format 10x15cm a 300dpi +LARGEUR_10x15 = 1200 +HAUTEUR_10x15 = 1800 -def creer_strip(photos: list[Path], marge: int = 20, couleur_fond: str = "#ffffff") -> Path: - """Cree une bande verticale (strip) avec les photos.""" + +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.""" + nb = len(photos) images = [Image.open(p) for p in photos] - # Uniformiser la largeur - largeur = min(img.width for img in images) + # 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 + ratio_cellule = largeur_cellule / hauteur_cellule + + # Recadrer et redimensionner chaque photo images_redim = [] for img in images: - ratio = largeur / img.width - hauteur = int(img.height * ratio) - images_redim.append(img.resize((largeur, hauteur), Image.LANCZOS)) + img_crop = _crop_center(img, ratio_cellule) + img_redim = img_crop.resize((largeur_cellule, hauteur_cellule), Image.LANCZOS) + images_redim.append(img_redim) - hauteur_totale = sum(img.height for img in images_redim) + marge * (len(images_redim) + 1) - largeur_totale = largeur + marge * 2 - - strip = Image.new("RGB", (largeur_totale, hauteur_totale), couleur_fond) + # Assembler sur le canvas 10x15 + strip = Image.new("RGB", (LARGEUR_10x15, HAUTEUR_10x15), couleur_fond) y = marge for img in images_redim: strip.paste(img, (marge, y)) - y += img.height + marge + y += hauteur_cellule + marge nom = f"strip_{photos[0].stem}.jpg" chemin = DOSSIER_EXPORTS / nom - strip.save(chemin, "JPEG", quality=95) - log.info(f"Strip cree : {chemin}") + strip.save(chemin, "JPEG", quality=95, dpi=(300, 300)) + log.info(f"Strip 10x15 cree : {chemin} ({nb} photos)") return chemin -def creer_collage(photos: list[Path], colonnes: int = 2, marge: int = 15, +def creer_collage(photos: list[Path], colonnes: int = 2, marge: int = 25, couleur_fond: str = "#ffffff") -> Path: - """Cree un collage en grille avec les photos.""" + """Cree un collage en grille au format 10x15cm.""" + nb = len(photos) images = [Image.open(p) for p in photos] - # Taille uniforme pour chaque cellule - largeur_cellule = min(img.width for img in images) - hauteur_cellule = min(img.height for img in images) + 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 = ImageDraw.Draw(img) - img_redim = img.resize((largeur_cellule, hauteur_cellule), Image.LANCZOS) + img_crop = _crop_center(img, ratio_cellule) + img_redim = img_crop.resize((largeur_cellule, hauteur_cellule), Image.LANCZOS) images_redim.append(img_redim) - lignes = (len(images_redim) + colonnes - 1) // colonnes - largeur_totale = colonnes * largeur_cellule + (colonnes + 1) * marge - hauteur_totale = lignes * hauteur_cellule + (lignes + 1) * marge - - collage = Image.new("RGB", (largeur_totale, hauteur_totale), couleur_fond) + # Assembler sur le canvas 10x15 + collage = Image.new("RGB", (LARGEUR_10x15, HAUTEUR_10x15), couleur_fond) for i, img in enumerate(images_redim): col = i % colonnes @@ -66,6 +91,6 @@ def creer_collage(photos: list[Path], colonnes: int = 2, marge: int = 15, nom = f"collage_{photos[0].stem}.jpg" chemin = DOSSIER_EXPORTS / nom - collage.save(chemin, "JPEG", quality=95) - log.info(f"Collage cree : {chemin}") + collage.save(chemin, "JPEG", quality=95, dpi=(300, 300)) + log.info(f"Collage 10x15 cree : {chemin} ({nb} photos, {colonnes}x{lignes})") return chemin diff --git a/backend/gif_maker.py b/backend/gif_maker.py deleted file mode 100644 index 99526df..0000000 --- a/backend/gif_maker.py +++ /dev/null @@ -1,41 +0,0 @@ -import logging -from pathlib import Path - -from PIL import Image - -from backend.config import DOSSIER_EXPORTS, charger_config - -log = logging.getLogger("photobooth.gif") - - -def creer_gif(photos: list[Path], duree_frame_ms: int | None = None) -> Path: - """Cree un GIF anime a partir d'une liste de photos.""" - config = charger_config() - if duree_frame_ms is None: - duree_frame_ms = config.get("gif", {}).get("duree_frame_ms", 300) - - images = [] - for p in photos: - img = Image.open(p).convert("RGB") - # Redimensionner pour un GIF leger - img.thumbnail((800, 600), Image.LANCZOS) - images.append(img) - - if not images: - log.error("Aucune image pour le GIF") - return None - - nom = f"gif_{photos[0].stem}.gif" - chemin = DOSSIER_EXPORTS / nom - - images[0].save( - chemin, - save_all=True, - append_images=images[1:], - duration=duree_frame_ms, - loop=0, - optimize=True, - ) - - log.info(f"GIF cree : {chemin} ({len(images)} frames, {duree_frame_ms}ms)") - return chemin diff --git a/backend/main.py b/backend/main.py index eb294b5..0b35edc 100644 --- a/backend/main.py +++ b/backend/main.py @@ -17,7 +17,7 @@ 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.gif_maker import creer_gif + from backend.printer import lister_imprimantes, imprimer from backend.mailer import envoyer_photo from backend.qrcode_gen import generer_qr, qr_galerie @@ -163,7 +163,7 @@ async def api_fonds(): return lister_fonds() -# --- API Collage / GIF --- +# --- API Collage --- @app.post("/api/strip") async def api_strip(donnees: dict): @@ -188,18 +188,6 @@ async def api_collage(donnees: dict): return {"nom": chemin.name, "chemin": f"/data/exports/{chemin.name}"} -@app.post("/api/gif") -async def api_gif(donnees: dict): - noms = donnees.get("photos", []) - chemins = [DOSSIER_PHOTOS / n for n in noms] - for c in chemins: - if not c.exists(): - return JSONResponse({"erreur": f"Photo introuvable : {c.name}"}, status_code=404) - chemin = creer_gif(chemins) - if chemin is None: - return JSONResponse({"erreur": "Echec creation GIF"}, status_code=500) - return {"nom": chemin.name, "chemin": f"/data/exports/{chemin.name}"} - # --- API Impression --- diff --git a/backend/requirements.txt b/backend/requirements.txt index 670572b..5ab6970 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -2,7 +2,6 @@ fastapi==0.115.0 uvicorn[standard]==0.30.0 python-gphoto2==2.5.0 Pillow==10.4.0 -imageio==2.35.0 opencv-python-headless==4.10.0.84 qrcode[pil]==7.4.2 pycups==2.0.4 diff --git a/data/config.json b/data/config.json index af04e5a..f8ce24e 100644 --- a/data/config.json +++ b/data/config.json @@ -9,7 +9,6 @@ "fonctionnalites": { "photo_simple": true, "multi_shot": true, - "gif": true, "filtres": true, "overlays": true, "chroma_key": false, @@ -46,11 +45,6 @@ "mode": "strip", "delai_entre_photos": 2 }, - "gif": { - "nombre_frames": 4, - "delai_entre_frames": 0.8, - "duree_frame_ms": 300 - }, "chroma_key": { "couleur": "#00ff00", "tolerance": 40, diff --git a/frontend/index.html b/frontend/index.html index db0f3cc..49d8710 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -34,10 +34,6 @@
🎴
Multi-shot - @@ -143,7 +139,6 @@
- diff --git a/frontend/js/admin.js b/frontend/js/admin.js index 8844ce5..e05768e 100644 --- a/frontend/js/admin.js +++ b/frontend/js/admin.js @@ -4,7 +4,6 @@ const TOGGLES_MAP = { 'tog-photo-simple': 'photo_simple', 'tog-multi-shot': 'multi_shot', - 'tog-gif': 'gif', 'tog-filtres': 'filtres', 'tog-overlays': 'overlays', 'tog-chroma-key': 'chroma_key', diff --git a/frontend/js/app.js b/frontend/js/app.js index 4e36197..52a94be 100644 --- a/frontend/js/app.js +++ b/frontend/js/app.js @@ -30,7 +30,6 @@ function appliquerConfig() { // Visibilite des modes toggleVisible('btn-multi', fonc.multi_shot); - toggleVisible('btn-gif', fonc.gif); toggleVisible('btn-imprimer', fonc.impression); toggleVisible('btn-email', fonc.email); toggleVisible('btn-qr', fonc.qr_code); diff --git a/frontend/js/camera.js b/frontend/js/camera.js index 4236b26..e0cda62 100644 --- a/frontend/js/camera.js +++ b/frontend/js/camera.js @@ -71,7 +71,6 @@ async function lancerCapture() { function getNombrePhotos() { if (modeActuel === 'simple') return 1; if (modeActuel === 'multi') return config.multi_shot?.nombre_photos || 3; - if (modeActuel === 'gif') return config.gif?.nombre_frames || 4; return 1; } @@ -109,14 +108,7 @@ async function traiterCapture() { return; } - if (modeActuel === 'gif') { - // Creer le GIF - const resultat = await apiPost('/api/gif', { photos: photosSession }); - if (resultat.nom) { - photoFinale = resultat.nom; - afficherPreviewPhoto('/data/exports/' + resultat.nom); - } - } else if (modeActuel === 'multi') { + if (modeActuel === 'multi') { // Creer le strip/collage const mode = config.multi_shot?.mode || 'strip'; let resultat;