Calibration impression : offsets coupe (mm) + rotation 180° configurable
- impression.offsets_coupe_mm : {haut, bas, gauche, droite} compense la coupe asymétrique
- impression.rotation_180 : retourne l'image si le driver inverse le sens
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user