L'application apps/visionneuse/app.py est appelée par l'Explorateur lorsque l'utilisateur double-clique sur un fichier image (.png, .jpg, .gif). Elle permet d'afficher l'image de façon proportionnelle tout en autorisant le zoom et le déplacement panoramique (cliquer-glisser).
Pillow (PIL.Image, PIL.ImageTk) capable de décoder n'importe quel format (JPEG, WebP, etc.).Label), l'image est peinte (create_image) sur un tk.Canvas. Cela permet d'avoir un contrôle total sur ses coordonnées X et Y à l'intérieur de la fenêtre.<MouseWheel> ou <Button-4>/<Button-5>), le script multiplie les dimensions actuelles de l'image par un ratio (ex: 1.1 pour agrandir, 0.9 pour rétrécir). Il redemande ensuite à Pillow de recalculer l'image (Image.ANTIALIAS) pour éviter que les pixels ne bavent trop, puis remplace l'ancienne image du Canvas par la nouvelle.image.rotate()), et un bouton pour la sauvegarder dans son nouveau sens.window.after(3000)) pour charger et afficher automatiquement la photo suivante toutes les 3 secondes.import tkinter as tk
from tkinter import messagebox
from PIL import Image, ImageTk
import os
def start(window, app_manager=None, **kwargs):
frame = tk.Frame(window, bg="black")
frame.pack(fill="both", expand=True)
def show_help():
msg = (
"Aide de la Visionneuse\n\n"
"• Cette application affiche les images redimensionnées pour s'adapter à la fenêtre.\n"
"• Redimensionnez la fenêtre pour agrandir l'image.\n"
"• Fermez avec la croix rouge pour quitter."
)
messagebox.showinfo("Aide Visionneuse", msg)
btn_help = tk.Button(frame, text=" ? Aide ", command=show_help, relief="flat", bg="lightblue")
btn_help.place(x=5, y=5)
lbl_img = tk.Label(frame, bg="black")
lbl_img.pack(fill="both", expand=True)
filepath = kwargs.get("filepath")
def update_title():
if hasattr(window, 'master') and window.master.__class__.__name__ == 'Window':
title_text = filepath if filepath else "Aucune image"
window.master.title_label.config(text=f"Visionneuse - {title_text}")
update_title()
if not filepath or not os.path.exists(filepath):
lbl_img.config(text="Fichier introuvable ou aucune image sélectionnée", fg="white")
return
try:
# Initial window sizing based on image
img = Image.open(filepath)
img_w, img_h = img.size
if hasattr(window, 'master') and window.master.__class__.__name__ == 'Window':
win_obj = window.master
parent_w = win_obj.parent.winfo_width() or win_obj.parent.winfo_screenwidth()
parent_h = win_obj.parent.winfo_height() or win_obj.parent.winfo_screenheight()
max_w = int(parent_w * 0.8)
max_h = int(parent_h * 0.8)
target_w = min(img_w, max_w)
target_h = min(img_h, max_h)
target_w = max(400, target_w)
target_h = max(300, target_h)
win_obj.place(width=target_w, height=target_h + 30)
except Exception as e:
print(f"Erreur initialisation image: {e}")
def load_image(event=None):
try:
# Assure the frame layout is calculated
frame.update_idletasks()
w = frame.winfo_width()
h = frame.winfo_height()
# Use reasonable default if too small
if w <= 10 or h <= 10:
w, h = 400, 300
img = Image.open(filepath)
img.thumbnail((w, h), Image.Resampling.LANCZOS)
photo = ImageTk.PhotoImage(img)
lbl_img.config(image=photo)
lbl_img.image = photo # Prevent garbage collection
except Exception as e:
lbl_img.config(text=f"Erreur de chargement:\n{e}", fg="red")
# Load on resize, but debounce or just once on start
frame.bind("<Configure>", lambda e: window.after(50, load_image))
window.after(50, load_image)