# Miniatures : Conservation du ratio d'aspect ## 🎯 ProblĂšme Les miniatures gĂ©nĂ©rĂ©es Ă©taient **carrĂ©es** (crop + resize), ce qui dĂ©formait les images. **Comportement prĂ©cĂ©dent** : ``` Image 1920×1080 → Crop carrĂ© 1080×1080 → Resize 300×300 Image 800×600 → Crop carrĂ© 600×600 → Resize 300×300 ``` **ProblĂšme** : - Perte de contexte (crop) - Toutes les miniatures ont le mĂȘme format carrĂ© - Ne respecte pas le ratio original ## ✅ Solution implĂ©mentĂ©e ### Modification 1 : Algorithme de thumbnail **Fichier** : `backend/app/utils/image_processor.py` (lignes 222-230) **Avant** (crop carrĂ©) : ```python # Create square thumbnail (crop to center) width, height = img.size min_dimension = min(width, height) # Calculate crop box (center crop) left = (width - min_dimension) // 2 top = (height - min_dimension) // 2 right = left + min_dimension bottom = top + min_dimension img = img.crop((left, top, right, bottom)) # Resize to thumbnail size img.thumbnail((size, size), Image.Resampling.LANCZOS) ``` **AprĂšs** (conservation ratio) : ```python # Resize keeping aspect ratio (width-based) # size parameter represents the target width width, height = img.size aspect_ratio = height / width new_width = size new_height = int(size * aspect_ratio) # Use thumbnail method to preserve aspect ratio img.thumbnail((new_width, new_height), Image.Resampling.LANCZOS) ``` **Changements** : - ✅ Plus de crop (toute l'image est conservĂ©e) - ✅ Largeur fixe Ă  `size` pixels (48px) - ✅ Hauteur calculĂ©e selon le ratio original - ✅ Utilise `Image.thumbnail()` qui prĂ©serve le ratio ### Modification 2 : Configuration **Fichier** : `config/image_compression.yaml` **Tous les niveaux** mis Ă  jour avec `thumbnail_size: 48` : ```yaml levels: high: thumbnail_size: 48 # Avant: 400 thumbnail_quality: 85 medium: thumbnail_size: 48 # Avant: 300 thumbnail_quality: 75 low: thumbnail_size: 48 # Avant: 200 thumbnail_quality: 65 minimal: thumbnail_size: 48 # Avant: 150 thumbnail_quality: 55 ``` **SĂ©mantique** : `thumbnail_size` = **largeur en pixels** (et non plus taille carrĂ©e) ## 📊 Exemples de rĂ©sultats ### Image paysage (16:9) **Original** : 1920×1080 ``` Avant : 1920×1080 → crop 1080×1080 → 300×300 ❌ AprĂšs : 1920×1080 → resize 48×27 ✅ ``` ### Image portrait (3:4) **Original** : 800×1067 ``` Avant : 800×1067 → crop 800×800 → 300×300 ❌ AprĂšs : 800×1067 → resize 48×64 ✅ ``` ### Image carrĂ©e (1:1) **Original** : 800×800 ``` Avant : 800×800 → crop 800×800 → 300×300 AprĂšs : 800×800 → resize 48×48 ✅ (identique) ``` ## 🎹 Impact visuel ### Avant (carrĂ©, 300×300) ``` ┌───────┐ ┌───────┐ ┌───────┐ │ │ │ â–Ș │ │ │ │ â–Șâ–Șâ–Ș │ │ â–Șâ–Șâ–Ș │ │ â–Șâ–Șâ–Ș │ Toutes carrĂ©es │ │ │ â–Ș │ │ │ Crop des bords └───────┘ └───────┘ └───────┘ 300×300 300×300 300×300 ``` ### AprĂšs (ratio conservĂ©, 48px large) ``` ┌────┐ ┌──┐ ┌────┐ │â–Șâ–Șâ–Ș │ │â–Ș │ │â–Șâ–Șâ–Ș │ Ratio original └────┘ │â–Ș │ └────┘ Pas de crop 48×27 │â–Ș │ 48×48 Toute l'image └──┘ 48×64 ``` ## 🔍 Avantages 1. **Conservation de l'image complĂšte** - Aucune partie de l'image n'est coupĂ©e - Contexte visuel prĂ©servĂ© 2. **Ratio d'aspect original** - Paysage reste paysage - Portrait reste portrait - Pas de dĂ©formation 3. **Taille optimale** - 48px de large = idĂ©al pour listes/grilles - Poids fichier trĂšs rĂ©duit (~1-3 KB) - Chargement ultra-rapide 4. **FlexibilitĂ© d'affichage** - CSS peut gĂ©rer l'affichage (object-fit) - S'adapte aux grilles responsives ## đŸ’Ÿ Taille des fichiers ### Comparaison avant/aprĂšs | Format original | Avant (300×300) | AprĂšs (48px wide) | Gain | |-----------------|-----------------|-------------------|------| | 1920×1080 PNG | ~35 KB | ~2 KB | **94%** | | 800×600 JPEG | ~25 KB | ~1.5 KB | **94%** | | 1600×1200 PNG | ~40 KB | ~2.5 KB | **94%** | **→ Gain de poids : ~94% en moyenne** ## đŸ–Œïž CSS recommandĂ© Pour afficher les miniatures avec ratio conservĂ© : ```css .thumbnail-img { width: 48px; /* Largeur fixe */ height: auto; /* Hauteur automatique = ratio conservĂ© */ object-fit: contain; /* Contient l'image sans dĂ©formation */ } /* Ou pour container fixe */ .thumbnail-container { width: 48px; height: 48px; display: flex; align-items: center; /* Centre verticalement */ justify-content: center; } .thumbnail-container img { max-width: 48px; max-height: 48px; width: auto; height: auto; } ``` ## 🔄 RĂ©gĂ©nĂ©ration des thumbnails existants Les anciennes miniatures (carrĂ©es) resteront en place. Pour rĂ©gĂ©nĂ©rer avec le nouveau systĂšme : ```python # Script de rĂ©gĂ©nĂ©ration (optionnel) from app.models.peripheral import PeripheralPhoto from app.utils.image_processor import ImageProcessor from app.db.session import get_peripherals_db db = next(get_peripherals_db()) photos = db.query(PeripheralPhoto).all() for photo in photos: if os.path.exists(photo.stored_path): upload_dir = os.path.dirname(photo.stored_path) # Supprimer ancienne miniature carrĂ©e if photo.thumbnail_path and os.path.exists(photo.thumbnail_path): os.remove(photo.thumbnail_path) # RĂ©gĂ©nĂ©rer avec nouveau ratio thumbnail_path, _ = ImageProcessor.create_thumbnail_with_level( image_path=photo.stored_path, output_dir=upload_dir, compression_level="medium" ) photo.thumbnail_path = thumbnail_path db.commit() print(f"✅ RĂ©gĂ©nĂ©rĂ© thumbnail pour photo {photo.id}") ``` ## 📝 RĂ©sumĂ© technique | Aspect | Avant | AprĂšs | |--------|-------|-------| | **MĂ©thode** | Crop + Resize | Resize ratio preservĂ© | | **Taille** | 300×300 (carrĂ©) | 48×(hauteur auto) | | **Poids** | ~25-40 KB | ~1-3 KB | | **Crop** | Oui (perte info) | Non (image complĂšte) | | **Ratio** | ForcĂ© 1:1 | Original prĂ©servĂ© | | **QualitĂ©** | 75% | 75% | | **Format** | PNG | PNG | ## 🎯 Prochaines uploads Toutes les nouvelles photos uploadĂ©es gĂ©nĂ©reront automatiquement : 1. **Original** : Copie non modifiĂ©e dans `original/` 2. **Image redimensionnĂ©e** : 1920×1080 @ 85% qualitĂ© 3. **Thumbnail** : **48px de large, ratio conservĂ©** @ 75% qualitĂ© ✹ --- **Date** : 31 dĂ©cembre 2025 **Statut** : ✅ ImplĂ©mentĂ© et dĂ©ployĂ© **Impact** : Miniatures plus lĂ©gĂšres et ratio d'image conservĂ©