fix: opacity 80% for impacts + lower quality for pics + removing slowly zoom if you back go back
This commit is contained in:
@@ -151,6 +151,19 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Repasse en mode calibration en réinitialisant le zoom de l'InteractiveViewer.
|
||||||
|
///
|
||||||
|
/// Sans cette remise à zéro, le facteur de zoom accumulé en mode Plotting
|
||||||
|
/// persiste dans le TransformationController et se réapplique au retour,
|
||||||
|
/// ce qui faisait "zoomer" légèrement la photo à chaque aller-retour
|
||||||
|
/// Plotting <-> Centrage. On repart donc toujours d'une transformation
|
||||||
|
/// identité (zoom 1.0).
|
||||||
|
void _enterCalibration() {
|
||||||
|
_transformationController.value = Matrix4.identity();
|
||||||
|
_currentZoomScale = 1.0;
|
||||||
|
setState(() => _isCalibrating = true);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final provider = context.watch<AnalysisProvider>();
|
final provider = context.watch<AnalysisProvider>();
|
||||||
@@ -184,7 +197,8 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
setState(() => _isCalibrating = true);
|
// Retour Plotting -> Calibration : on réinitialise le zoom.
|
||||||
|
_enterCalibration();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -199,7 +213,16 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
|
|||||||
),
|
),
|
||||||
if (_isCalibrating)
|
if (_isCalibrating)
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => setState(() => _isCalibrating = false),
|
onPressed: () {
|
||||||
|
// On fige la calibration courante dans le provider AVANT de
|
||||||
|
// basculer en Plotting, pour que l'instance Plotting parte
|
||||||
|
// d'un état figé et indépendant des rebuilds de calibration.
|
||||||
|
_calibrationKey.currentState?.commitCalibration();
|
||||||
|
setState(() {
|
||||||
|
_isCalibrating = false;
|
||||||
|
_isSelectingReferences = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'TERMINER',
|
'TERMINER',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -316,7 +339,8 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
|
|||||||
Icons.arrow_forward_ios,
|
Icons.arrow_forward_ios,
|
||||||
size: 16,
|
size: 16,
|
||||||
),
|
),
|
||||||
onTap: () => setState(() => _isCalibrating = true),
|
// Retour vers la calibration : on réinitialise le zoom.
|
||||||
|
onTap: () => _enterCalibration(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|||||||
@@ -83,6 +83,16 @@ class TargetCalibrationState extends State<TargetCalibration> {
|
|||||||
_initRingRadii();
|
_initRingRadii();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fige et propage la calibration courante vers le provider.
|
||||||
|
///
|
||||||
|
/// Appelée par l'écran d'analyse (via GlobalKey) juste avant de basculer
|
||||||
|
/// dans l'instance de Plotting, pour garantir que l'état affiché en Plotting
|
||||||
|
/// correspond exactement au dernier réglage validé, sans dépendre d'un
|
||||||
|
/// éventuel rebuild intermédiaire.
|
||||||
|
void commitCalibration() {
|
||||||
|
_notifyChange();
|
||||||
|
}
|
||||||
|
|
||||||
void _initRingRadii({bool forceRecalculate = false}) {
|
void _initRingRadii({bool forceRecalculate = false}) {
|
||||||
// CORRECTION : Si on ne recalcule pas activement l'espacement linéaire, on préserve en priorité la structure d'origine
|
// CORRECTION : Si on ne recalcule pas activement l'espacement linéaire, on préserve en priorité la structure d'origine
|
||||||
if (!forceRecalculate && _originalRingRadii != null && _originalRingRadii!.length == _ringCount) {
|
if (!forceRecalculate && _originalRingRadii != null && _originalRingRadii!.length == _ringCount) {
|
||||||
|
|||||||
@@ -211,8 +211,8 @@ class _TargetOverlayPainter extends CustomPainter {
|
|||||||
final prevMultiplier = i == 0
|
final prevMultiplier = i == 0
|
||||||
? 0.0
|
? 0.0
|
||||||
: (ringRadii != null && ringRadii!.length == ringCount)
|
: (ringRadii != null && ringRadii!.length == ringCount)
|
||||||
? ringRadii![i - 1]
|
? ringRadii![i - 1]
|
||||||
: i / ringCount;
|
: i / ringCount;
|
||||||
final zoneRadius = maxRadius * (currentMultiplier + prevMultiplier) / 2;
|
final zoneRadius = maxRadius * (currentMultiplier + prevMultiplier) / 2;
|
||||||
final score = 10 - i;
|
final score = 10 - i;
|
||||||
|
|
||||||
@@ -292,16 +292,17 @@ class _TargetOverlayPainter extends CustomPainter {
|
|||||||
final strokeWidth = 3 / zoomScale;
|
final strokeWidth = 3 / zoomScale;
|
||||||
final fontSize = 10 / zoomScale;
|
final fontSize = 10 / zoomScale;
|
||||||
|
|
||||||
// Draw outer circle (white outline for visibility)
|
// Draw outer circle (white outline for visibility) — gardé OPAQUE pour
|
||||||
|
// bien repérer le centre même quand le remplissage est transparent.
|
||||||
final outlinePaint = Paint()
|
final outlinePaint = Paint()
|
||||||
..color = AppTheme.impactOutlineColor
|
..color = AppTheme.impactOutlineColor
|
||||||
..style = PaintingStyle.stroke
|
..style = PaintingStyle.stroke
|
||||||
..strokeWidth = strokeWidth;
|
..strokeWidth = strokeWidth;
|
||||||
canvas.drawCircle(Offset(x, y), outerRadius, outlinePaint);
|
canvas.drawCircle(Offset(x, y), outerRadius, outlinePaint);
|
||||||
|
|
||||||
// Draw impact marker
|
// Draw impact marker — TRANSPARENCE 50% pour voir l'impact réel derrière
|
||||||
final impactPaint = Paint()
|
final impactPaint = Paint()
|
||||||
..color = AppTheme.impactColor
|
..color = AppTheme.impactColor.withValues(alpha: 0.2)
|
||||||
..style = PaintingStyle.fill;
|
..style = PaintingStyle.fill;
|
||||||
canvas.drawCircle(Offset(x, y), innerRadius, impactPaint);
|
canvas.drawCircle(Offset(x, y), innerRadius, impactPaint);
|
||||||
|
|
||||||
@@ -381,4 +382,4 @@ class _TargetOverlayPainter extends CustomPainter {
|
|||||||
zoomScale != oldDelegate.zoomScale ||
|
zoomScale != oldDelegate.zoomScale ||
|
||||||
showRings != oldDelegate.showRings;
|
showRings != oldDelegate.showRings;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,7 +244,8 @@ class _CaptureScreenState extends State<CaptureScreen>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await File(tempPath).writeAsBytes(img.encodeJpg(converted, quality: 60));
|
// PERF : qualité 50 pour la frame d'analyse temps réel (jetable)
|
||||||
|
await File(tempPath).writeAsBytes(img.encodeJpg(converted, quality: 50));
|
||||||
|
|
||||||
final result = await _opencvService.detectTarget(tempPath);
|
final result = await _opencvService.detectTarget(tempPath);
|
||||||
|
|
||||||
@@ -735,7 +736,7 @@ class _CaptureScreenState extends State<CaptureScreen>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
// Capture manuelle (inchangée, sauf ajout stop IMU)
|
// Capture manuelle (MODIFIÉE : ajout dégradation post-capture pour perf)
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
Future<void> _takePictureManually() async {
|
Future<void> _takePictureManually() async {
|
||||||
if (_cameraController == null || !_cameraController!.value.isInitialized) {
|
if (_cameraController == null || !_cameraController!.value.isInitialized) {
|
||||||
@@ -784,6 +785,15 @@ class _CaptureScreenState extends State<CaptureScreen>
|
|||||||
finalPath = photo.path; // on garde la photo originale en secours
|
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,
|
||||||
|
// et accélère nettement le décodage à chaque écran (chargement plotting).
|
||||||
|
try {
|
||||||
|
finalPath = await _downscaleForPipeline(finalPath);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Downscale ignoré: $e');
|
||||||
|
}
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedImagePath = finalPath;
|
_selectedImagePath = finalPath;
|
||||||
_showLiveCamera = false;
|
_showLiveCamera = false;
|
||||||
@@ -797,6 +807,38 @@ class _CaptureScreenState extends State<CaptureScreen>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Réduit fortement la résolution de la photo pour accélérer tous les
|
||||||
|
/// écrans suivants (crop, calibration, plotting). 1080 px de côté max
|
||||||
|
/// + JPEG qualité 70 : largement suffisant pour la détection visuelle.
|
||||||
|
///
|
||||||
|
/// 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
|
||||||
|
);
|
||||||
|
|
||||||
|
final tempDir = await getTemporaryDirectory();
|
||||||
|
final outPath =
|
||||||
|
'${tempDir.path}/downscaled_${DateTime.now().millisecondsSinceEpoch}.jpg';
|
||||||
|
await File(outPath).writeAsBytes(img.encodeJpg(resized, quality: 70));
|
||||||
|
return outPath;
|
||||||
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
// Helpers UI (inchangés)
|
// Helpers UI (inchangés)
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
@@ -861,9 +903,10 @@ class _CaptureScreenState extends State<CaptureScreen>
|
|||||||
try {
|
try {
|
||||||
final XFile? image = await _picker.pickImage(
|
final XFile? image = await _picker.pickImage(
|
||||||
source: ImageSource.gallery,
|
source: ImageSource.gallery,
|
||||||
maxWidth: 2048,
|
// PERF : limite d'import plus agressive pour accélérer le pipeline
|
||||||
maxHeight: 2048,
|
maxWidth: 1280,
|
||||||
imageQuality: 90,
|
maxHeight: 1280,
|
||||||
|
imageQuality: 75,
|
||||||
);
|
);
|
||||||
if (image != null) {
|
if (image != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|||||||
Reference in New Issue
Block a user