- Intégration selphy_print (backend CUPS dyesub Mitsubishi CP-K60DW-S) - Formats impression : 15x20, 10x15, 10x15 2-strips avec cadre et orientation portrait/paysage - Cadres d'impression par format (strip/10x15/15x20) : upload, sélection, aperçu live - Cadres démo générés : pellicule noir/vintage/couleurs + bordures classique/doré/rose gold - Écran de choix de cadre avant capture (clic direct, aperçu grand format) - Filtres réduits à 3 (Couleur, N&B, Sépia) et appliqués sur tous les strips - Port 80 via authbind, route /admin avec redirection auto depuis poste distant - Autologin LightDM corrigé (pam-autologin-service) - Bouton évacuer bourrage papier dans admin - Fix : _gp_lock manquant dans Camera.__init__ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
179 lines
6.8 KiB
Python
179 lines
6.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Génère des cadres de démonstration pour chaque format d'impression."""
|
|
from pathlib import Path
|
|
from PIL import Image, ImageDraw
|
|
|
|
RACINE = Path(__file__).resolve().parent.parent
|
|
DOSSIER_CADRES = RACINE / "frontend" / "assets" / "cadres"
|
|
|
|
FORMATS = {
|
|
"strip": (600, 1800),
|
|
"10x15": (1200, 1800),
|
|
"15x20": (1800, 2400),
|
|
}
|
|
|
|
|
|
# ─── Cadres simples (tous formats) ───────────────────────────────────────────
|
|
|
|
def cadre_simple(largeur, hauteur, couleur_bord, epaisseur=30, couleur_coin=None):
|
|
img = Image.new("RGBA", (largeur, hauteur), (0, 0, 0, 0))
|
|
d = ImageDraw.Draw(img)
|
|
cc = couleur_coin or couleur_bord
|
|
ep = epaisseur
|
|
# 4 bords
|
|
d.rectangle([0, 0, largeur, ep], fill=couleur_bord)
|
|
d.rectangle([0, hauteur - ep, largeur, hauteur], fill=couleur_bord)
|
|
d.rectangle([0, 0, ep, hauteur], fill=couleur_bord)
|
|
d.rectangle([largeur - ep, 0, largeur, hauteur], fill=couleur_bord)
|
|
# coins
|
|
tc = ep + 20
|
|
for x, y in [(0, 0), (largeur - tc, 0), (0, hauteur - tc), (largeur - tc, hauteur - tc)]:
|
|
d.rectangle([x, y, x + tc, y + tc], fill=cc)
|
|
# ligne intérieure fine
|
|
m = ep + 8
|
|
d.rectangle([m, m, largeur - m, hauteur - m], outline=cc, width=2)
|
|
return img
|
|
|
|
|
|
# ─── Cadres pellicule (format strip uniquement) ───────────────────────────────
|
|
|
|
def cadre_pellicule(largeur, hauteur, couleur_bord=(15, 15, 15, 255),
|
|
couleur_perf=(50, 50, 50, 255), label=""):
|
|
"""Cadre pellicule avec perforations sur les côtés gauche et droit."""
|
|
img = Image.new("RGBA", (largeur, hauteur), (0, 0, 0, 0))
|
|
d = ImageDraw.Draw(img)
|
|
|
|
bord = 52 # largeur de la bande pellicule de chaque côté
|
|
perf_h = 22 # hauteur d'une perforation
|
|
perf_w = 16 # largeur d'une perforation
|
|
perf_r = 4 # rayon arrondi
|
|
perf_gap = 14 # espace entre perforations
|
|
|
|
# Bandes noires gauche et droite
|
|
d.rectangle([0, 0, bord, hauteur], fill=couleur_bord)
|
|
d.rectangle([largeur - bord, 0, largeur, hauteur], fill=couleur_bord)
|
|
|
|
# Bande fine haut et bas
|
|
bord_h = 10
|
|
d.rectangle([0, 0, largeur, bord_h], fill=couleur_bord)
|
|
d.rectangle([0, hauteur - bord_h, largeur, hauteur], fill=couleur_bord)
|
|
|
|
# Perforations gauche
|
|
y = perf_gap
|
|
while y + perf_h < hauteur:
|
|
x = (bord - perf_w) // 2
|
|
d.rounded_rectangle([x, y, x + perf_w, y + perf_h], radius=perf_r, fill=couleur_perf)
|
|
y += perf_h + perf_gap
|
|
|
|
# Perforations droite
|
|
y = perf_gap
|
|
while y + perf_h < hauteur:
|
|
x = largeur - bord + (bord - perf_w) // 2
|
|
d.rounded_rectangle([x, y, x + perf_w, y + perf_h], radius=perf_r, fill=couleur_perf)
|
|
y += perf_h + perf_gap
|
|
|
|
# Ligne intérieure fine pour délimiter la zone photo
|
|
m = bord + 4
|
|
d.rectangle([m, bord_h + 4, largeur - m, hauteur - bord_h - 4], outline=(80, 80, 80, 180), width=1)
|
|
|
|
return img
|
|
|
|
|
|
def cadre_pellicule_vintage(largeur, hauteur):
|
|
"""Pellicule couleur sépia avec numéros de frame."""
|
|
img = Image.new("RGBA", (largeur, hauteur), (0, 0, 0, 0))
|
|
d = ImageDraw.Draw(img)
|
|
|
|
bord = 58
|
|
bord_sepia = (40, 28, 12, 255)
|
|
perf_col = (90, 65, 30, 255)
|
|
perf_h, perf_w, perf_r, perf_gap = 20, 14, 3, 16
|
|
|
|
d.rectangle([0, 0, bord, hauteur], fill=bord_sepia)
|
|
d.rectangle([largeur - bord, 0, largeur, hauteur], fill=bord_sepia)
|
|
d.rectangle([0, 0, largeur, 8], fill=bord_sepia)
|
|
d.rectangle([0, hauteur - 8, largeur, hauteur], fill=bord_sepia)
|
|
|
|
for side in [0, 1]:
|
|
y = perf_gap
|
|
while y + perf_h < hauteur:
|
|
x = (bord - perf_w) // 2 if side == 0 else largeur - bord + (bord - perf_w) // 2
|
|
d.rounded_rectangle([x, y, x + perf_w, y + perf_h], radius=perf_r, fill=perf_col)
|
|
y += perf_h + perf_gap
|
|
|
|
# Filet intérieur
|
|
m = bord + 5
|
|
d.rectangle([m, 12, largeur - m, hauteur - 12], outline=(100, 75, 35, 200), width=2)
|
|
return img
|
|
|
|
|
|
def cadre_pellicule_couleur(largeur, hauteur, teinte=(220, 50, 50)):
|
|
"""Pellicule colorée style photobooth rétro."""
|
|
img = Image.new("RGBA", (largeur, hauteur), (0, 0, 0, 0))
|
|
d = ImageDraw.Draw(img)
|
|
|
|
bord = 50
|
|
r, g, b = teinte
|
|
col_bord = (r, g, b, 255)
|
|
col_perf = (min(255, r + 60), min(255, g + 60), min(255, b + 60), 255)
|
|
perf_h, perf_w, perf_r, perf_gap = 20, 14, 4, 14
|
|
|
|
d.rectangle([0, 0, bord, hauteur], fill=col_bord)
|
|
d.rectangle([largeur - bord, 0, largeur, hauteur], fill=col_bord)
|
|
d.rectangle([0, 0, largeur, 8], fill=col_bord)
|
|
d.rectangle([0, hauteur - 8, largeur, hauteur], fill=col_bord)
|
|
|
|
for side in [0, 1]:
|
|
y = perf_gap
|
|
while y + perf_h < hauteur:
|
|
x = (bord - perf_w) // 2 if side == 0 else largeur - bord + (bord - perf_w) // 2
|
|
d.rounded_rectangle([x, y, x + perf_w, y + perf_h], radius=perf_r, fill=col_perf)
|
|
y += perf_h + perf_gap
|
|
|
|
m = bord + 5
|
|
d.rectangle([m, 12, largeur - m, hauteur - 12], outline=col_perf, width=2)
|
|
return img
|
|
|
|
|
|
# ─── Main ─────────────────────────────────────────────────────────────────────
|
|
|
|
def main():
|
|
# Cadres simples pour tous les formats
|
|
SIMPLES = [
|
|
("classique_blanc.png", (255, 255, 255, 255), (210, 210, 210, 255), 30),
|
|
("elegant_noir.png", (20, 20, 20, 255), (70, 70, 70, 255), 40),
|
|
("rose_gold.png", (198, 143, 115, 255), (230, 180, 150, 255), 35),
|
|
("festif_dore.png", (212, 175, 55, 255), (255, 215, 0, 255), 32),
|
|
]
|
|
|
|
for fmt, (larg, haut) in FORMATS.items():
|
|
dossier = DOSSIER_CADRES / fmt
|
|
dossier.mkdir(parents=True, exist_ok=True)
|
|
|
|
for nom, col_bord, col_coin, ep in SIMPLES:
|
|
cadre = cadre_simple(larg, haut, col_bord, ep, col_coin)
|
|
cadre.save(dossier / nom, "PNG")
|
|
print(f" ✓ {fmt}/{nom}")
|
|
|
|
# Cadres pellicule — uniquement format strip
|
|
dossier_strip = DOSSIER_CADRES / "strip"
|
|
larg, haut = FORMATS["strip"]
|
|
|
|
pellicules = [
|
|
("pellicule_noir.png", cadre_pellicule(larg, haut)),
|
|
("pellicule_vintage.png", cadre_pellicule_vintage(larg, haut)),
|
|
("pellicule_rouge.png", cadre_pellicule_couleur(larg, haut, (200, 40, 40))),
|
|
("pellicule_bleu.png", cadre_pellicule_couleur(larg, haut, (30, 80, 180))),
|
|
("pellicule_vert.png", cadre_pellicule_couleur(larg, haut, (30, 140, 60))),
|
|
]
|
|
|
|
for nom, img in pellicules:
|
|
img.save(dossier_strip / nom, "PNG")
|
|
print(f" ✓ strip/{nom} (pellicule)")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print("Génération des cadres de démonstration...")
|
|
main()
|
|
print("Terminé.")
|