# 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

```python
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)
```
