|
- // lib/services/stable_diffusion_service.dart
-
- import 'dart:async';
- import 'dart:convert';
- 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';
-
- // --- Définitions des filtres (isolés pour être utilisés avec compute) ---
-
- // Look-up table pour les couleurs
- typedef ColorLut = ({List<int> r, List<int> g, List<int> b});
-
- // Filtre 1: Applique un filtre chaud via une LUT
- Uint8List _applyWarmthFilter((Uint8List, ColorLut) params) {
- final imageBytes = params.$1;
- final lut = params.$2;
- final image = img.decodeImage(imageBytes);
- if (image == null) return imageBytes;
-
- for (final pixel in image) {
- pixel.r = lut.r[pixel.r.toInt()];
- pixel.g = lut.g[pixel.g.toInt()];
- pixel.b = lut.b[pixel.b.toInt()];
- }
- return Uint8List.fromList(img.encodeJpg(image, quality: 95));
- }
-
- // Filtre 2: Augmente le contraste et la saturation
- Uint8List _applyContrastSaturationFilter(Uint8List imageBytes) {
- final image = img.decodeImage(imageBytes);
- if (image == null) return imageBytes;
-
- img.adjustColor(image, contrast: 1.2, saturation: 1.15);
-
- return Uint8List.fromList(img.encodeJpg(image, quality: 95));
- }
-
- // Calcule la LUT pour le filtre chaud
- ColorLut _computeWarmthLut() {
- final rLut = List.generate(256, (i) => (i * 1.15).clamp(0, 255).toInt());
- final gLut = List.generate(256, (i) => (i * 1.05).clamp(0, 255).toInt());
- final bLut = List.generate(256, (i) => (i * 0.90).clamp(0, 255).toInt());
- return (r: rLut, g: gLut, b: bLut);
- }
-
-
- 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}) {
- final controller = StreamController<String>();
- _generateVariations(controller, base64Image, prompt, width, height);
- return controller.stream;
- }
-
- /// NOUVELLE LOGIQUE DE GÉNÉRATION, PLUS EFFICACE
- Future<void> _generateVariations(StreamController<String> controller, String base64Image, String prompt, int width, int height) async {
- try {
- // 1. On fait UN SEUL appel à Stable Diffusion pour une variation de base.
- final response = await _createImageRequest(
- base64Image: base64Image,
- prompt: "$prompt, high quality, sharp focus", // Prompt générique de qualité
- width: width,
- height: height,
- denoisingStrength: 0.30, // Assez pour une variation notable
- cfgScale: 7.0,
- );
-
- if (response.statusCode != 200) {
- throw Exception('Erreur Stable Diffusion ${response.statusCode}: ${response.body}');
- }
-
- final responseData = jsonDecode(response.body);
- final generatedImageBase64 = (responseData['images'] as List).first as String;
-
- // La variation de base est notre première image.
- controller.add(generatedImageBase64);
-
- // 2. On prépare les filtres à appliquer sur cette variation de base.
- final generatedImageBytes = base64Decode(generatedImageBase64);
- final warmthLut = _computeWarmthLut();
-
- // 3. On lance les deux filtres en parallèle pour plus de rapidité.
- final futureWarm = compute(_applyWarmthFilter, (generatedImageBytes, warmthLut));
- final futureContrast = compute(_applyContrastSaturationFilter, generatedImageBytes);
-
- // 4. On attend les résultats des filtres et on les ajoute au stream.
- final results = await Future.wait([futureWarm, futureContrast]);
-
- for (final filteredBytes in results) {
- controller.add(base64Encode(filteredBytes));
- }
-
- } catch (e, stackTrace) {
- controller.addError(e, stackTrace);
- } finally {
- controller.close(); // On ferme le stream quand tout est fini.
- }
- }
-
- Future<http.Response> _createImageRequest({
- required String base64Image,
- required String prompt,
- required int width,
- required int height,
- required double denoisingStrength,
- required double cfgScale,
- }) {
- final requestBody = {
- 'init_images': [base64Image],
- 'prompt': prompt,
- 'seed': -1,
- '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',
- 'steps': 25, // 25 est souvent suffisant pour img2img
- 'cfg_scale': cfgScale,
- 'denoising_strength': denoisingStrength,
- 'sampler_name': 'DPM++ 2M 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: 5));
- }
- }
|