Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

144 lines
4.7KB

  1. // lib/services/stable_diffusion_service.dart
  2. import 'dart:convert';
  3. import 'dart:async';
  4. import 'dart:typed_data';
  5. import 'package:flutter/foundation.dart';
  6. import 'package:http/http.dart' as http;
  7. import 'package:image/image.dart' as img;
  8. import 'image_editing_service.dart';
  9. class StableDiffusionService implements ImageEditingService {
  10. final String _apiUrl = 'http://192.168.20.200:7860/sdapi/v1/img2img';
  11. @override
  12. Future<String> generatePrompt(String base64Image) async {
  13. throw UnimplementedError('Stable Diffusion ne peut pas générer de prompt.');
  14. }
  15. @override
  16. Stream<String> editImage(
  17. String base64Image,
  18. String prompt,
  19. int width,
  20. int height, {
  21. int numberOfImages = 3,
  22. }) {
  23. return Stream.fromFuture(() async {
  24. print("[StableDiffusionService] Redimensionnement de l'image d'entrée...");
  25. // 1. Décoder l'image d'entrée.
  26. final originalImageBytes = base64Decode(base64Image);
  27. final originalImage = img.decodeImage(originalImageBytes);
  28. if (originalImage == null) {
  29. throw Exception("Impossible de décoder l'image d'entrée pour le redimensionnement.");
  30. }
  31. // 2. Redimensionner l'image proprement aux dimensions cibles (SANS la rogner).
  32. final resizedImage = img.copyResize(
  33. originalImage,
  34. width: width,
  35. height: height,
  36. interpolation: img.Interpolation.average, // Algorithme de qualité
  37. );
  38. // 3. Ré-encoder l'image redimensionnée pour l'envoyer à l'API.
  39. final resizedBase64Image = base64Encode(img.encodeJpg(resizedImage));
  40. // --- FIN DE LA CORRECTION ---
  41. print("[StableDiffusionService] Préparation de 3 requêtes parallèles...");
  42. final basePrompt = prompt;
  43. final warmPrompt = "$prompt, warm golden hour lighting, soft golden glow, rich warm tones, amber light";
  44. // On utilise l'image redimensionnée (`resizedBase64Image`) pour toutes les requêtes.
  45. final requests = [
  46. _createImageRequest(resizedBase64Image, basePrompt, width, height),
  47. _createImageRequest(resizedBase64Image, warmPrompt, width, height),
  48. _createImageRequest(resizedBase64Image, warmPrompt, width, height),
  49. ];
  50. try {
  51. final responses = await Future.wait(requests);
  52. final imagesBase64 = responses.map((response) {
  53. final responseData = jsonDecode(response.body);
  54. return (responseData['images'] as List).first as String;
  55. }).toList();
  56. if (imagesBase64.length < 3) {
  57. throw Exception('Stable Diffusion n\'a pas retourné 3 images.');
  58. }
  59. print("[StableDiffusionService] Application du filtre de chaleur...");
  60. final warmedImages = await Future.wait([
  61. _applyWarmthFilter(base64Decode(imagesBase64[1])),
  62. _applyWarmthFilter(base64Decode(imagesBase64[2])),
  63. ]);
  64. print("[StableDiffusionService] Filtre appliqué. Retour des 3 images.");
  65. return [
  66. imagesBase64[0],
  67. base64Encode(warmedImages[0]),
  68. base64Encode(warmedImages[1]),
  69. ];
  70. } catch (e) {
  71. print("Erreur lors des requêtes parallèles à Stable Diffusion: $e");
  72. rethrow;
  73. }
  74. }())
  75. .expand((images) => images);
  76. }
  77. // ... (le reste de la classe, _createImageRequest etc., est inchangé)
  78. /// Méthode privée pour créer une requête HTTP POST pour une seule image.
  79. Future<http.Response> _createImageRequest(String base64Image, String prompt, int width, int height) {
  80. // Le corps de la requête est maintenant simple, sans batch_size
  81. final requestBody = {
  82. 'init_images': [base64Image],
  83. 'prompt': prompt, // un seul prompt (string)
  84. 'seed': -1, // un seul seed (integer)
  85. 'negative_prompt': 'blurry, low quality, artifacts, distorted, oversaturated, plastic skin, over-sharpen, artificial, harsh shadows, filters, watermark, cold lighting, blue tones, washed out, unnatural',
  86. 'steps': 30, // Un peu moins de steps car les requêtes sont en parallèle
  87. 'cfg_scale': 6.8,
  88. 'denoising_strength': 0.25,
  89. 'sampler_name': 'DPM++ 2M Karras',
  90. 'scheduler': 'karras',
  91. 'width': width,
  92. 'height': height,
  93. 'restore_faces': false,
  94. };
  95. return http.post(
  96. Uri.parse(_apiUrl),
  97. headers: {'Content-Type': 'application/json'},
  98. body: jsonEncode(requestBody),
  99. ).timeout(const Duration(minutes: 10));
  100. }
  101. /// Fonction de post-traitement pour ajouter un filtre chaud à une image.
  102. Future<Uint8List> _applyWarmthFilter(Uint8List imageBytes) async {
  103. return await compute(_processingWarmthFilter, imageBytes);
  104. }
  105. }
  106. // Fonction globale pour le 'compute'
  107. Uint8List _processingWarmthFilter(Uint8List imageBytes) {
  108. final image = img.decodeImage(imageBytes);
  109. if (image == null) return imageBytes;
  110. for (final pixel in image) {
  111. pixel.r = (pixel.r * 1.15).clamp(0, 255).toInt();
  112. pixel.g = (pixel.g * 1.05).clamp(0, 255).toInt();
  113. pixel.b = (pixel.b * 0.90).clamp(0, 255).toInt();
  114. }
  115. return Uint8List.fromList(img.encodeJpg(image, quality: 95));
  116. }