# Annexe : Application - Visionneuse d'Images

## Rôle et utilité

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).

## Implémentation technique

- Pillow (PIL) au cœur du rendu : Tkinter natif ne sait lire que des formats d'images basiques (comme le GIF ou des vieux formats PPM). C'est pourquoi la visionneuse utilise la puissante librairie Python `Pillow` (`PIL.Image`, `PIL.ImageTk`) capable de décoder n'importe quel format (JPEG, WebP, etc.).
- Le Canvas comme surface de dessin : Plutôt que de mettre l'image dans une simple étiquette statique (`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.
- Zoom Mathématique : Lorsque l'utilisateur utilise la molette de la souris (`<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.

## Pistes de modification

- Boutons de rotation : Ajouter une petite barre d'outils en bas de l'écran avec deux boutons permettant de faire tourner l'image de 90° vers la droite ou vers la gauche (`image.rotate()`), et un bouton pour la sauvegarder dans son nouveau sens.
- Diaporama (Slideshow) : Si l'utilisateur clique sur un bouton "Diaporama", le script pourrait analyser le dossier actuel, lister toutes les images, et utiliser un appel asynchrone (`window.after(3000)`) pour charger et afficher automatiquement la photo suivante toutes les 3 secondes.

## Code Source

    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)