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:
|
orientation: str = "paysage", event_id: str | None = None) -> Path:
|
||||||
"""
|
"""
|
||||||
Pivote si nécessaire, recadre aux dimensions exactes (cover crop),
|
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:
|
try:
|
||||||
from PIL import Image, ImageOps
|
from PIL import Image, ImageOps
|
||||||
@@ -91,12 +91,10 @@ def _preparer_image(chemin: Path, largeur: int, hauteur: int,
|
|||||||
return chemin
|
return chemin
|
||||||
|
|
||||||
img = Image.open(chemin).convert("RGB")
|
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:
|
if orientation == "portrait" and img.width > img.height:
|
||||||
img = img.rotate(90, expand=True)
|
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:
|
elif orientation == "paysage" and img.height > img.width:
|
||||||
img = img.rotate(-90, expand=True)
|
img = img.rotate(-90, expand=True)
|
||||||
|
|
||||||
@@ -105,12 +103,52 @@ def _preparer_image(chemin: Path, largeur: int, hauteur: int,
|
|||||||
if cadre and format_papier:
|
if cadre and format_papier:
|
||||||
img = appliquer_cadre_impression(img, format_papier, cadre, event_id=event_id)
|
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)
|
tmp = tempfile.NamedTemporaryFile(suffix=".jpg", delete=False)
|
||||||
img.save(tmp.name, "JPEG", quality=95, dpi=(300, 300))
|
img.save(tmp.name, "JPEG", quality=95, dpi=(300, 300))
|
||||||
log.debug(f"Image préparée : {orientation} {largeur}x{hauteur}px → {tmp.name}")
|
log.debug(f"Image préparée : {orientation} {largeur}x{hauteur}px → {tmp.name}")
|
||||||
return Path(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:
|
def _dupliquer_page(chemin: Path, taille_page: tuple[int, int]) -> Path:
|
||||||
"""Colle la bande 2 fois cote a cote pour remplir la feuille entiere.
|
"""Colle la bande 2 fois cote a cote pour remplir la feuille entiere.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user