diff --git a/backend/printer.py b/backend/printer.py index 6aabe27..ea9b2b6 100644 --- a/backend/printer.py +++ b/backend/printer.py @@ -82,7 +82,7 @@ def _preparer_image(chemin: Path, largeur: int, hauteur: int, orientation: str = "paysage", event_id: str | None = None) -> Path: """ Pivote si nécessaire, recadre aux dimensions exactes (cover crop), - applique le cadre, sauvegarde en temp. + applique le cadre, compense les marges de coupe, sauvegarde en temp. """ try: from PIL import Image, ImageOps @@ -91,12 +91,10 @@ def _preparer_image(chemin: Path, largeur: int, hauteur: int, return chemin img = Image.open(chemin).convert("RGB") - img = ImageOps.exif_transpose(img) # respecte l'orientation EXIF + img = ImageOps.exif_transpose(img) - # Portrait demandé mais DSLR paysage → rotation 90° if orientation == "portrait" and img.width > img.height: img = img.rotate(90, expand=True) - # Paysage demandé mais image portrait (ex. prise avec DSLR tourné) → rotation -90° elif orientation == "paysage" and img.height > img.width: img = img.rotate(-90, expand=True) @@ -105,12 +103,52 @@ def _preparer_image(chemin: Path, largeur: int, hauteur: int, if cadre and format_papier: img = appliquer_cadre_impression(img, format_papier, cadre, event_id=event_id) + img = _appliquer_offsets_coupe(img) + + config_imp = charger_config().get("impression", {}) + if config_imp.get("rotation_180", False): + img = img.rotate(180) + tmp = tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) img.save(tmp.name, "JPEG", quality=95, dpi=(300, 300)) log.debug(f"Image préparée : {orientation} {largeur}x{hauteur}px → {tmp.name}") return Path(tmp.name) +def _appliquer_offsets_coupe(img): + """Décale le contenu de l'image pour compenser les marges de coupe asymétriques. + Config impression.offsets_coupe_mm : {haut, bas, gauche, droite} en mm.""" + from PIL import Image + config = charger_config() + offsets = config.get("impression", {}).get("offsets_coupe_mm", {}) + if not offsets: + return img + + DPI = 300 + mm_px = DPI / 25.4 + haut = int(offsets.get("haut", 0) * mm_px) + bas = int(offsets.get("bas", 0) * mm_px) + gauche = int(offsets.get("gauche", 0) * mm_px) + droite = int(offsets.get("droite", 0) * mm_px) + + if haut == 0 and bas == 0 and gauche == 0 and droite == 0: + return img + + w, h = img.size + nouvelle = Image.new("RGB", (w, h), (255, 255, 255)) + # Décale : offset positif = l'imprimante coupe trop de ce côté, on décale le contenu vers l'opposé + # crop la source pour que le contenu tienne malgré le décalage + src_x = max(0, droite - gauche) + src_y = max(0, bas - haut) + dst_x = max(0, gauche - droite) + dst_y = max(0, haut - bas) + crop_w = w - abs(gauche - droite) + crop_h = h - abs(haut - bas) + region = img.crop((src_x, src_y, src_x + crop_w, src_y + crop_h)) + nouvelle.paste(region, (dst_x, dst_y)) + return nouvelle + + def _dupliquer_page(chemin: Path, taille_page: tuple[int, int]) -> Path: """Colle la bande 2 fois cote a cote pour remplir la feuille entiere.