|
- // 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<String> generatePrompt(String base64Image) async {
- throw UnimplementedError('Stable Diffusion ne peut pas générer de prompt.');
- }
-
- @override
- Stream<String> 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<http.Response> _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<Uint8List> _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));
- }
|