import 'dart:typed_data'; import 'package:image/image.dart' as img; /// Compression des images à l'upload. /// /// Un seul point d'entrée, appelé par les **deux** chemins d'upload de /// `resources_screen`. Côté serveur, ces deux chemins avaient divergé sur /// `StoragePath` puis sur le contrôle de quota (C1 et C3) : la même erreur /// coûterait ici des images compressées d'un côté et pas de l'autre. class ImageCompressor { /// Côté le plus long. Au-delà, une photo n'apporte rien à l'écran d'un /// visiteur et coûte du stockage, du transfert et du temps de visite hors ligne. static const int maxDimension = 2560; static const int jpegQuality = 82; static const List _compressibleExtensions = ['jpg', 'jpeg', 'png']; static bool handles(String? extension) => extension != null && _compressibleExtensions.contains(extension.toLowerCase()); /// Renvoie les octets à téléverser et le type MIME correspondant. /// /// L'original est renvoyé tel quel si le format n'est pas gérable, si le /// décodage échoue, ou si la compression n'a rien gagné — une image déjà /// petite et bien encodée peut grossir en repassant par un ré-encodage. static CompressedImage compress(Uint8List bytes, String? extension, String fallbackMimeType) { if (!handles(extension)) { return CompressedImage(bytes, fallbackMimeType, false); } try { final decoded = img.decodeImage(bytes); if (decoded == null) { return CompressedImage(bytes, fallbackMimeType, false); } final resized = (decoded.width > maxDimension || decoded.height > maxDimension) ? img.copyResize( decoded, width: decoded.width >= decoded.height ? maxDimension : null, height: decoded.height > decoded.width ? maxDimension : null, interpolation: img.Interpolation.average, ) : decoded; // Un PNG à canal alpha reste un PNG : le passer en JPEG remplirait la // transparence de noir, ce qui abîmerait les logos et les filigranes. final keepsAlpha = decoded.hasAlpha; final encoded = keepsAlpha ? Uint8List.fromList(img.encodePng(resized)) : Uint8List.fromList(img.encodeJpg(resized, quality: jpegQuality)); if (encoded.length >= bytes.length) { return CompressedImage(bytes, fallbackMimeType, false); } return CompressedImage(encoded, keepsAlpha ? 'image/png' : 'image/jpeg', true); } catch (e) { print("ERROR ImageCompressor.compress"); print(e); return CompressedImage(bytes, fallbackMimeType, false); } } } class CompressedImage { final Uint8List bytes; final String mimeType; final bool wasCompressed; CompressedImage(this.bytes, this.mimeType, this.wasCompressed); int get sizeBytes => bytes.length; }