- Created a pre-commit hook in .husky to run lint-staged. - Added .lintstagedrc.mjs to format frontend files with Prettier and apply Spotless to backend Java files. - Introduced .prettierignore to exclude specific directories from formatting.
20 KiB
Issue #7 — normalisation WebP côté backend Java
Superseded: the Java-library recommendation in this note was rejected in favor of ImageMagick. See Issue #7 — socle pérenne de normalisation des photos and ADR 0017. The capability and limitation findings below remain historical research.
Recherche arrêtée au 23 juillet 2026. Les conclusions ci-dessous reposent uniquement sur les API, dépôts et métadonnées de publication des projets concernés.
Conclusion
La proposition « JPEG/PNG/WebP, 20 Mo, 50 mégapixels » ne découle d'aucune limite de bibliothèque. Les codecs ne fournissent pas une politique de sécurité prête à l'emploi et ne justifient donc ni 20 Mo ni 50 MP.
L'option minimale qui couvre l'ADR-0017 est :
net.coobird:thumbnailator:0.4.21pour l'orientation EXIF JPEG, le calcul de taille et le redimensionnement ;com.github.usefulness:webp-imageio:0.11.0pour l'encodeur WebP lossy, explicitement épinglé car Spring Boot ne le gère pas ;- les lecteurs ImageIO du JDK pour les formats d'entrée.
Elle permet une implémentation courte, mais avec trois précautions indispensables :
size(1920, 1080)de Thumbnailator agrandit une petite image. Pour respecter « sans upscaling », il faut calculerscale = min(1, 1920 / largeur_orientée, 1080 / hauteur_orientée)ou utiliserscale(1)quand l'image orientée tient déjà dans le cadre.- L'auto-orientation de Thumbnailator est centrée sur l'EXIF d'un JPEG (arbre
javax_imageio_jpeg_image_1.0ou segment APP1). Elle n'est pas une garantie générale pour l'EXIF embarqué dans un TIFF, PNG ou WebP. - Ni Thumbnailator ni
webp-imageion'impose un plafond de pixels protecteur. Le plafond d'octets existant ne protège pas seul contre une image très compressée demandant beaucoup de mémoire une fois décodée. Le seuil opérationnel doit faire l'objet d'une décision séparée, mesurée avec la mémoire allouée au conteneur, et non être présenté comme une contrainte du codec.
Pour les seuls uploads photo usuels JPEG/PNG, c'est le meilleur rapport simplicité/couverture. Si le ticket exige une orientation EXIF uniforme pour tous les conteneurs acceptés, remplacer la lecture EXIF de Thumbnailator par metadata-extractor et appliquer explicitement l'une des huit transformations est plus robuste, au prix de code métier supplémentaire.
Ce que fournit le JDK 25 seul
L'API ImageIO du JDK 25 garantit des lecteurs et écrivains pour BMP, GIF, JPEG, PNG, TIFF et WBMP. WebP ne fait pas partie des plug-ins standards. Source : documentation Java SE 25 de javax.imageio.
Conséquences :
- entrées réellement décodables sans dépendance : BMP, GIF, JPEG, PNG, TIFF, WBMP ;
- sortie WebP : impossible sans plug-in ou outil externe ;
- redimensionnement/rotation : possible avec Java2D (
BufferedImage,Graphics2D,AffineTransform), donc aucune dépendance n'est techniquement nécessaire pour les pixels ; - orientation EXIF : ImageIO ne l'applique pas automatiquement. Le lecteur JPEG expose notamment les marqueurs APP dans son arbre de métadonnées, mais il faut analyser l'APP1/TIFF et appliquer la transformation soi-même ;
- métadonnées :
IIOImagepermet de les transmettre à un écrivain, mais une image réencodée avec des métadonnéesnullne recopie pas celles de l'original ; - animation/multipage : le plug-in GIF sait écrire des séquences et les lecteurs peuvent exposer plusieurs images, mais la commodité
ImageIO.read(...)ne lit que l'image d'index 0.
Le JDK seul échoue donc sur deux exigences du ticket : WebP et auto-orientation EXIF. Réimplémenter un parseur EXIF pour éviter une petite dépendance n'est pas l'option la plus simple ni la moins risquée.
Option recommandée : Thumbnailator + usefulness/webp-imageio
Capacités réelles
| Sujet | Comportement vérifié |
|---|---|
| Formats d'entrée | Ceux des lecteurs ImageIO présents : avec le JDK et webp-imageio, BMP, GIF, JPEG, PNG, TIFF, WBMP et WebP. Le type MIME et l'extension ne sont pas nécessaires à la sélection du lecteur. HEIC/HEIF et AVIF ne sont pas décodés par cette combinaison. |
| WebP en sortie | Oui, lossy ou lossless. Le plug-in enregistre un ImageWriter ImageIO WebP. |
| Qualité 75 | Oui. Thumbnailator transmet 0.75f à ImageWriteParam.setCompressionQuality; WebPWriteParam multiplie cette valeur par 100 pour libwebp. Il faut conserver le type de compression Lossy. |
| Orientation EXIF | Oui pour les huit valeurs d'orientation JPEG. Activée par défaut dans Thumbnailator et activable explicitement avec useExifOrientation(true). Le code ajoute la rotation/réflexion avant les autres filtres et échange les dimensions pour les orientations 5 à 8. |
| Portée EXIF | JPEG surtout : le parseur lit l'arbre natif JPEG et son repli capture le segment APP1. Le lecteur usefulness retourne toujours null pour les métadonnées WebP ; un WebP portant une orientation EXIF ne sera donc pas réorienté par cette combinaison. |
| Métadonnées de sortie | Supprimées : Thumbnailator appelle l'écrivain avec new IIOImage(img, null, null) et l'écrivain usefulness retourne null pour ses métadonnées par défaut/conversion. Le WebP produit ne reprend donc ni EXIF, ni XMP, ni GPS, ni miniature. |
| Animation | Thumbnailator lit l'image 0. Son usage ordinaire aplatit donc un GIF multipage sur sa première image. Le lecteur usefulness annonce toujours une seule image et n'expose pas l'animation WebP. Il n'y a pas de rejet automatique uniforme des animations. |
| Redimensionnement | Conservation du ratio et filtres de redimensionnement fournis. Attention : size(1920, 1080) peut agrandir ; le non-upscaling nécessite le min(1, ...) explicite. |
| Dépendances natives | webp-imageio utilise JNI et embarque libwebp. Binaires présents pour Linux x86, x86_64, ARM/ARMv6/ARMv7/AArch64, PPC64 ; macOS x86_64/AArch64 ; Windows x86/x86_64. Aucun paquet système libwebp n'est à installer sur ces cibles. |
| Java/JDK | Thumbnailator 0.4.21 produit un JAR Java ancien et sans dépendance runtime propre. webp-imageio annonce Java 11, cible un bytecode ancien et teste actuellement les JDK 17, 21 et 24. Java 25 n'est pas déclaré dans sa matrice ; sa compatibilité doit donc rester couverte par un test d'intégration du projet sous le JDK 25 réel. Aucune intégration Spring spécifique n'est requise. |
| Licences | Thumbnailator : MIT. usefulness/webp-imageio : Apache-2.0 ; binaires libwebp sous la licence du projet libwebp. |
| Maintenance | Thumbnailator 0.4.21 a été publié le 1er octobre 2025 et son dépôt reste actif en 2026. usefulness/webp-imageio 0.11.0 a été publié le 15 juin 2026 avec libwebp 1.6.0. |
| Limites/protections | Aucun plafond de pixels documenté. Le lecteur usefulness met le flux WebP compressé entier dans un byte[], refuse seulement une longueur connue supérieure à Integer.MAX_VALUE, puis alloue le raster décodé. Thumbnailator contient un mécanisme optionnel de sous-échantillonnage dépendant de la mémoire libre, mais ce n'est ni une validation d'upload ni une limite contractuelle. |
Sources primaires :
- README, plateformes, usage, licence de usefulness/webp-imageio
- release 0.11.0 et contraintes glibc
- lecteur usefulness : une image, flux en
byte[], aucune métadonnée - écrivain usefulness : aucune métadonnée, encodage RGB/RGBA
WebPWriteParam: qualité ImageIO 0..1 vers libwebp 0..100- binaires natifs embarqués
- matrice de CI usefulness
- source de l'orientation EXIF Thumbnailator
- transformations des huit orientations
- ordre orientation puis lecture/redimensionnement
- sortie Thumbnailator sans métadonnées et transmission de la qualité
- publication et licence Thumbnailator
Forme minimale de l'implémentation
Le traitement doit rester explicite sur les étapes suivantes :
- ouvrir un
ImageInputStreamet sélectionner le lecteur d'après le contenu ; - lire largeur, hauteur, nombre d'images quand le lecteur sait le fournir, et l'orientation EXIF avant le raster complet ;
- déterminer les dimensions après orientation ;
- calculer
min(1, 1920 / largeur, 1080 / hauteur)afin de ne jamais agrandir ; - lire, orienter et redimensionner ;
- écrire avec le writer WebP en
Lossy, qualité0.75f, etIIOImage(..., null, null); - rouvrir le résultat, vérifier dimensions/format, puis seulement attribuer la référence stable et supprimer l'original comme prévu par l'ADR.
Thumbnailator raccourcit les étapes 3 à 6 ; il ne dispense pas de la validation de la source ni de la décision sur les animations et les ressources.
Alternative : Java2D + usefulness/webp-imageio + metadata-extractor
com.drewnoakes:metadata-extractor lit l'EXIF, notamment l'orientation, dans JPEG, TIFF, WebP, PNG et plusieurs autres conteneurs. Il ne décode pas les pixels, ne redimensionne pas et n'écrit pas WebP. Sa version actuelle cible Java 8, n'emploie pas de code natif et est sous Apache-2.0.
Cette variante conserve les mêmes entrées pixel réellement décodables que JDK + usefulness, mais rend la lecture de l'orientation indépendante du lecteur ImageIO. Il faut ensuite écrire explicitement les huit transformations AffineTransform, le calcul de taille et le redimensionnement Java2D. L'écriture et les contraintes natives restent celles de usefulness/webp-imageio.
Avantage : comportement EXIF plus large et contrôlable, erreurs de métadonnées observables. Inconvénient : plus de code sensible qu'avec Thumbnailator. À choisir uniquement si l'exigence porte bien sur l'EXIF de plusieurs conteneurs, et pas seulement sur les JPEG de téléphone.
Sources primaires : formats et usage de metadata-extractor, POM, Java 8 et licence, constante TAG_ORIENTATION de l'IFD0.
Alternative : Scrimage WebP
com.sksamuel.scrimage:scrimage-webp:4.6.6 offre une API plus intégrée : lecture ImageIO, metadata-extractor, auto-orientation activée par défaut, bound(...) pour réduire sans agrandir et new WebpWriter().withQ(75).
Elle n'est pas la solution minimale pour ce backend :
- l'artefact WebP entraîne
scrimage-core, TwelveMonkeys JPEG, metadata-extractor, commons-io, pngj, commons-lang3 et SLF4J ; - l'écriture passe par un PNG temporaire puis exécute
cwebpdans un processus ; - la bibliothèque extrait des exécutables embarqués dans un fichier temporaire et doit pouvoir les exécuter ;
- les binaires embarqués couvrent Linux x86_64/AArch64, macOS x86_64/ARM64 et Windows x64, moins de cibles que usefulness ;
- les ressources du module contiennent libwebp 1.4.0, alors que usefulness 0.11.0 embarque 1.6.0 ;
- le timeout de processus est de cinq minutes, mais aucune limite de pixels ou d'octets n'est imposée ;
- le projet cible Java 11 et est activement publié en 2026 sous Apache-2.0.
Scrimage sait aussi traiter les animations via des API dédiées, fonctionnalité inutile au ticket tant qu'aucune politique d'animation n'est décidée.
Sources primaires : dépôt et release actuelle Scrimage, dépendances de scrimage-core, écrivain WebP et qualité, exécution de cwebp, plateformes et extraction des binaires, auto-orientation par défaut.
TwelveMonkeys ImageIO
TwelveMonkeys 3.14.x est activement maintenu, Java 8+, pur Java et BSD-3-Clause. Son plug-in WebP sait lire, y compris les trames d'animation, et analyser ICC/EXIF/XMP ; il ne sait pas écrire WebP. Son tableau de formats indique explicitement R = oui, W = non.
Il peut améliorer la validation d'un WebP entrant ou apporter un lecteur JPEG plus tolérant (notamment aux variantes rencontrées dans la nature), mais ne remplace pas usefulness pour la sortie. L'associer à usefulness installe deux lecteurs WebP concurrents, ajoute plusieurs JARs et ne réalise toujours pas automatiquement l'orientation. Ce n'est donc pas justifié pour la première implémentation de l'issue #7.
Sources primaires : table des formats TwelveMonkeys, source du lecteur WebP et des animations/métadonnées, licence BSD-3-Clause, versions Maven Central.
Décisions qui restent au produit, pas à la bibliothèque
Formats acceptés
La combinaison recommandée peut décoder BMP, GIF, JPEG, PNG, TIFF, WBMP et WebP. Cela ne signifie pas que l'API doit tous les accepter. Pour un parcours « photo de constat », JPEG et PNG couvrent le cas minimal ; accepter WebP entrant est simple mais son orientation EXIF n'est pas couverte par Thumbnailator. HEIC/HEIF exige un autre décodeur et n'est fourni par aucune option minimale ci-dessus.
La validation doit utiliser le lecteur capable de décoder le flux, pas seulement l'extension ou le Content-Type déclaré.
Animations et fichiers multipages
Les bibliothèques ne convergent pas vers une politique : GIF/TIFF peuvent contenir plusieurs images, usefulness ne représente pas l'animation WebP, Thumbnailator choisit l'image 0, TwelveMonkeys expose les trames. Il faut décider explicitement entre rejet et aplatissement sur la première image ; aucun des deux choix ne découle d'une limite technique.
Octets et pixels
Aucun seuil « 20 Mo » ou « 50 MP » n'est fourni ou recommandé par ces bibliothèques. Les faits techniques sont seulement :
- le multipart Spring possède déjà une limite globale configurable ;
- les pixels décodés consomment typiquement au moins
largeur × hauteur × 4octets, auxquels s'ajoutent les buffers du lecteur, de l'image orientée, du redimensionnement et de l'encodeur ; - usefulness charge en plus le WebP compressé complet en mémoire ;
- une limite d'octets compressés ne borne pas la mémoire du raster ;
- les dimensions peuvent être interrogées avant le décodage complet avec ImageIO pour prendre une décision.
La prochaine décision devrait donc partir du budget mémoire et du parallélisme réels du conteneur, avec des images adversariales et des photos de téléphones représentatives. En l'absence de cette mesure, conserver provisoirement la limite multipart existante et ne pas inventer un plafond « métier » présenté comme une capacité de la bibliothèque ; en revanche, le décodage d'uploads non fiables devra recevoir une protection de ressources avant exposition publique.
Recommandation finale pour l'issue #7
Adopter Thumbnailator 0.4.21 + usefulness/webp-imageio 0.11.0, restreindre initialement l'entrée aux formats réellement requis par le parcours, et tester sous JDK 25 :
- les huit orientations EXIF JPEG ;
- paysage/portrait au-dessus et au-dessous de 1920 × 1080, en vérifiant l'absence d'agrandissement ;
- sortie WebP lossy à
0.75f; - absence d'EXIF/XMP/GPS dans le résultat ;
- comportement choisi pour GIF/TIFF/WebP animés ;
- échec propre d'un flux tronqué ou non-image ;
- disponibilité du binaire JNI dans l'image Docker de production.
Ne fixer aucun nouveau seuil octets/pixels dans cette décision de bibliothèque. Le documenter séparément une fois le budget mémoire et la concurrence connus.