fix: freeze et deformation lors de la prise de la photo (rotation)

This commit is contained in:
qguillaume
2026-06-11 20:04:25 +02:00
parent 064f5c1fe3
commit 331fda0ffb

View File

@@ -1,6 +1,7 @@
import 'dart:io';
import 'dart:math' as math;
import 'dart:async';
import 'dart:isolate';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:permission_handler/permission_handler.dart';
@@ -17,7 +18,6 @@ import 'widgets/image_source_button.dart';
import '../../services/image_crop_service.dart';
import '../../services/opencv_target_service.dart';
import '../../services/parallelism_service.dart'; // NOUVEAU
import '../../services/target_rectify_service.dart'; // NOUVEAU : redressement
import 'package:image/image.dart' as img;
import 'package:path_provider/path_provider.dart';
@@ -64,9 +64,6 @@ class _CaptureScreenState extends State<CaptureScreen>
ParallelismData? _parallelismData;
StreamSubscription<ParallelismData>? _parallelismSubscription;
// Service de redressement de cible (warp perspective)
final TargetRectifyService _rectifyService = TargetRectifyService();
@override
void initState() {
super.initState();
@@ -735,36 +732,11 @@ class _CaptureScreenState extends State<CaptureScreen>
final XFile photo = await _cameraController!.takePicture();
// NOUVEAU : redressement automatique de la cible (warp perspective).
// On écrit le résultat dans un fichier dédié, à côté de l'original.
// Pas de redressement perspective automatique ici : il déformait l'image
// (écrasement en carré + rotation de l'axe de l'ellipse) et gelait le
// thread UI. Le recentrage ET la rotation se font manuellement à l'écran
// de centrage suivant, qui est précis et non destructeur.
String finalPath = photo.path;
try {
final tempDir = await getTemporaryDirectory();
final rectifiedPath =
'${tempDir.path}/rectified_${DateTime.now().millisecondsSinceEpoch}.jpg';
final result = await _rectifyService.rectify(
inputPath: photo.path,
outputPath: rectifiedPath,
);
finalPath = result.outputPath;
if (mounted && result.rectified) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
duration: const Duration(seconds: 2),
content: Text(
'Cible redressée automatiquement '
'(inclinaison ${result.estimatedTiltDegrees.toStringAsFixed(1)}° corrigée)',
),
),
);
}
} catch (e) {
debugPrint('Redressement ignoré: $e');
finalPath = photo.path; // on garde la photo originale en secours
}
// PERF : on dégrade fortement la photo juste après la capture.
// Une résolution réduite suffit pour la calibration et le plotting,
@@ -795,29 +767,35 @@ class _CaptureScreenState extends State<CaptureScreen>
/// Baisser `maxSide` (ex. 900) ou `quality` (ex. 60) rend le chargement
/// encore plus rapide au prix d'un peu de finesse à l'écran.
Future<String> _downscaleForPipeline(String sourcePath) async {
final bytes = await File(sourcePath).readAsBytes();
final decoded = img.decodeImage(bytes);
if (decoded == null) return sourcePath;
const int maxSide = 1080;
final int longest = math.max(decoded.width, decoded.height);
if (longest <= maxSide) {
return sourcePath; // déjà assez petite, rien à faire
}
final double ratio = maxSide / longest;
final resized = img.copyResize(
decoded,
width: (decoded.width * ratio).round(),
height: (decoded.height * ratio).round(),
interpolation: img.Interpolation.linear, // rapide
);
// On résout le chemin de sortie sur le thread UI (path_provider a besoin
// du canal de plateforme), puis tout le travail lourd (décodage, resize,
// réencodage JPEG) part dans un Isolate → aucun gel du thread UI.
final tempDir = await getTemporaryDirectory();
final outPath =
'${tempDir.path}/downscaled_${DateTime.now().millisecondsSinceEpoch}.jpg';
await File(outPath).writeAsBytes(img.encodeJpg(resized, quality: 70));
return outPath;
return Isolate.run(() {
final bytes = File(sourcePath).readAsBytesSync();
final decoded = img.decodeImage(bytes);
if (decoded == null) return sourcePath;
const int maxSide = 1080;
final int longest = math.max(decoded.width, decoded.height);
if (longest <= maxSide) {
return sourcePath; // déjà assez petite, rien à faire
}
final double ratio = maxSide / longest;
final resized = img.copyResize(
decoded,
width: (decoded.width * ratio).round(),
height: (decoded.height * ratio).round(),
interpolation: img.Interpolation.linear, // rapide
);
File(outPath).writeAsBytesSync(img.encodeJpg(resized, quality: 70));
return outPath;
});
}
// ─────────────────────────────────────────────────────────────────────────