// lib/services/stable_diffusion_service.dart import 'dart:convert'; import 'dart:async'; import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:image/image.dart' as img; import 'image_editing_service.dart'; class StableDiffusionService implements ImageEditingService { final String _apiUrl = 'http://192.168.20.200:7860/sdapi/v1/img2img'; @override Future generatePrompt(String base64Image) async { throw UnimplementedError('Stable Diffusion ne peut pas générer de prompt.'); } @override Stream editImage( String base64Image, String prompt, int width, int height, { int numberOfImages = 3, }) { return Stream.fromFuture(() async { print("[StableDiffusionService] Redimensionnement de l'image d'entrée..."); // 1. Décoder l'image d'entrée. final originalImageBytes = base64Decode(base64Image); final originalImage = img.decodeImage(originalImageBytes); if (originalImage == null) { throw Exception("Impossible de décoder l'image d'entrée pour le redimensionnement."); } // 2. Redimensionner l'image proprement aux dimensions cibles (SANS la rogner). final resizedImage = img.copyResize( originalImage, width: width, height: height, interpolation: img.Interpolation.average, // Algorithme de qualité ); // 3. Ré-encoder l'image redimensionnée pour l'envoyer à l'API. final resizedBase64Image = base64Encode(img.encodeJpg(resizedImage)); // --- FIN DE LA CORRECTION --- print("[StableDiffusionService] Préparation de 3 requêtes parallèles..."); final basePrompt = prompt; final warmPrompt = "$prompt, warm golden hour lighting, soft golden glow, rich warm tones, amber light"; // On utilise l'image redimensionnée (`resizedBase64Image`) pour toutes les requêtes. final requests = [ _createImageRequest(resizedBase64Image, basePrompt, width, height), _createImageRequest(resizedBase64Image, warmPrompt, width, height), _createImageRequest(resizedBase64Image, warmPrompt, width, height), ]; try { final responses = await Future.wait(requests); final imagesBase64 = responses.map((response) { final responseData = jsonDecode(response.body); return (responseData['images'] as List).first as String; }).toList(); if (imagesBase64.length < 3) { throw Exception('Stable Diffusion n\'a pas retourné 3 images.'); } print("[StableDiffusionService] Application du filtre de chaleur..."); final warmedImages = await Future.wait([ _applyWarmthFilter(base64Decode(imagesBase64[1])), _applyWarmthFilter(base64Decode(imagesBase64[2])), ]); print("[StableDiffusionService] Filtre appliqué. Retour des 3 images."); return [ imagesBase64[0], base64Encode(warmedImages[0]), base64Encode(warmedImages[1]), ]; } catch (e) { print("Erreur lors des requêtes parallèles à Stable Diffusion: $e"); rethrow; } }()) .expand((images) => images); } // ... (le reste de la classe, _createImageRequest etc., est inchangé) /// Méthode privée pour créer une requête HTTP POST pour une seule image. Future _createImageRequest(String base64Image, String prompt, int width, int height) { // Le corps de la requête est maintenant simple, sans batch_size final requestBody = { 'init_images': [base64Image], 'prompt': prompt, // un seul prompt (string) 'seed': -1, // un seul seed (integer) 'negative_prompt': 'blurry, low quality, artifacts, distorted, oversaturated, plastic skin, over-sharpen, artificial, harsh shadows, filters, watermark, cold lighting, blue tones, washed out, unnatural', 'steps': 30, // Un peu moins de steps car les requêtes sont en parallèle 'cfg_scale': 6.8, 'denoising_strength': 0.25, 'sampler_name': 'DPM++ 2M Karras', 'scheduler': 'karras', 'width': width, 'height': height, 'restore_faces': false, }; return http.post( Uri.parse(_apiUrl), headers: {'Content-Type': 'application/json'}, body: jsonEncode(requestBody), ).timeout(const Duration(minutes: 10)); } /// Fonction de post-traitement pour ajouter un filtre chaud à une image. Future _applyWarmthFilter(Uint8List imageBytes) async { return await compute(_processingWarmthFilter, imageBytes); } } // Fonction globale pour le 'compute' Uint8List _processingWarmthFilter(Uint8List imageBytes) { final image = img.decodeImage(imageBytes); if (image == null) return imageBytes; for (final pixel in image) { pixel.r = (pixel.r * 1.15).clamp(0, 255).toInt(); pixel.g = (pixel.g * 1.05).clamp(0, 255).toInt(); pixel.b = (pixel.b * 0.90).clamp(0, 255).toInt(); } return Uint8List.fromList(img.encodeJpg(image, quality: 95)); }