Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

139 lines
4.9KB

  1. // lib/services/stable_diffusion_service.dart
  2. import 'dart:async';
  3. import 'dart:convert';
  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. // --- Définitions des filtres (isolés pour être utilisés avec compute) ---
  10. // Look-up table pour les couleurs
  11. typedef ColorLut = ({List<int> r, List<int> g, List<int> b});
  12. // Filtre 1: Applique un filtre chaud via une LUT
  13. Uint8List _applyWarmthFilter((Uint8List, ColorLut) params) {
  14. final imageBytes = params.$1;
  15. final lut = params.$2;
  16. final image = img.decodeImage(imageBytes);
  17. if (image == null) return imageBytes;
  18. for (final pixel in image) {
  19. pixel.r = lut.r[pixel.r.toInt()];
  20. pixel.g = lut.g[pixel.g.toInt()];
  21. pixel.b = lut.b[pixel.b.toInt()];
  22. }
  23. return Uint8List.fromList(img.encodeJpg(image, quality: 95));
  24. }
  25. // Filtre 2: Augmente le contraste et la saturation
  26. Uint8List _applyContrastSaturationFilter(Uint8List imageBytes) {
  27. final image = img.decodeImage(imageBytes);
  28. if (image == null) return imageBytes;
  29. img.adjustColor(image, contrast: 1.2, saturation: 1.15);
  30. return Uint8List.fromList(img.encodeJpg(image, quality: 95));
  31. }
  32. // Calcule la LUT pour le filtre chaud
  33. ColorLut _computeWarmthLut() {
  34. final rLut = List.generate(256, (i) => (i * 1.15).clamp(0, 255).toInt());
  35. final gLut = List.generate(256, (i) => (i * 1.05).clamp(0, 255).toInt());
  36. final bLut = List.generate(256, (i) => (i * 0.90).clamp(0, 255).toInt());
  37. return (r: rLut, g: gLut, b: bLut);
  38. }
  39. class StableDiffusionService implements ImageEditingService {
  40. final String _apiUrl = 'http://192.168.20.200:7860/sdapi/v1/img2img';
  41. @override
  42. Future<String> generatePrompt(String base64Image) async {
  43. throw UnimplementedError('Stable Diffusion ne peut pas générer de prompt.');
  44. }
  45. @override
  46. Stream<String> editImage(String base64Image, String prompt, int width, int height, {int numberOfImages = 3}) {
  47. final controller = StreamController<String>();
  48. _generateVariations(controller, base64Image, prompt, width, height);
  49. return controller.stream;
  50. }
  51. /// NOUVELLE LOGIQUE DE GÉNÉRATION, PLUS EFFICACE
  52. Future<void> _generateVariations(StreamController<String> controller, String base64Image, String prompt, int width, int height) async {
  53. try {
  54. // 1. On fait UN SEUL appel à Stable Diffusion pour une variation de base.
  55. final response = await _createImageRequest(
  56. base64Image: base64Image,
  57. prompt: "$prompt, high quality, sharp focus", // Prompt générique de qualité
  58. width: width,
  59. height: height,
  60. denoisingStrength: 0.30, // Assez pour une variation notable
  61. cfgScale: 7.0,
  62. );
  63. if (response.statusCode != 200) {
  64. throw Exception('Erreur Stable Diffusion ${response.statusCode}: ${response.body}');
  65. }
  66. final responseData = jsonDecode(response.body);
  67. final generatedImageBase64 = (responseData['images'] as List).first as String;
  68. // La variation de base est notre première image.
  69. controller.add(generatedImageBase64);
  70. // 2. On prépare les filtres à appliquer sur cette variation de base.
  71. final generatedImageBytes = base64Decode(generatedImageBase64);
  72. final warmthLut = _computeWarmthLut();
  73. // 3. On lance les deux filtres en parallèle pour plus de rapidité.
  74. final futureWarm = compute(_applyWarmthFilter, (generatedImageBytes, warmthLut));
  75. final futureContrast = compute(_applyContrastSaturationFilter, generatedImageBytes);
  76. // 4. On attend les résultats des filtres et on les ajoute au stream.
  77. final results = await Future.wait([futureWarm, futureContrast]);
  78. for (final filteredBytes in results) {
  79. controller.add(base64Encode(filteredBytes));
  80. }
  81. } catch (e, stackTrace) {
  82. controller.addError(e, stackTrace);
  83. } finally {
  84. controller.close(); // On ferme le stream quand tout est fini.
  85. }
  86. }
  87. Future<http.Response> _createImageRequest({
  88. required String base64Image,
  89. required String prompt,
  90. required int width,
  91. required int height,
  92. required double denoisingStrength,
  93. required double cfgScale,
  94. }) {
  95. final requestBody = {
  96. 'init_images': [base64Image],
  97. 'prompt': prompt,
  98. 'seed': -1,
  99. 'negative_prompt': 'blurry, low quality, artifacts, distorted, oversaturated, plastic skin, over-sharpen, artificial, harsh shadows, filters, watermark, cold lighting, blue tones, washed out, unnatural, sepia, text, letters',
  100. 'steps': 25, // 25 est souvent suffisant pour img2img
  101. 'cfg_scale': cfgScale,
  102. 'denoising_strength': denoisingStrength,
  103. 'sampler_name': 'DPM++ 2M Karras',
  104. 'width': width,
  105. 'height': height,
  106. 'restore_faces': false,
  107. };
  108. return http.post(
  109. Uri.parse(_apiUrl),
  110. headers: {'Content-Type': 'application/json'},
  111. body: jsonEncode(requestBody),
  112. ).timeout(const Duration(minutes: 5));
  113. }
  114. }