fix(crop): stabiliser le rectangle géométrique de découpe et l'enchaînement des zooms

This commit is contained in:
qguillaume
2026-05-27 15:36:56 +02:00
parent 4d2ca2c94f
commit 730629a031
5 changed files with 269 additions and 174 deletions

View File

@@ -156,7 +156,7 @@ class _CropScreenState extends State<CropScreen> {
style: TextStyle(color: Colors.white70, fontSize: 13, fontWeight: FontWeight.w600),
),
Text(
'${_rotation.toStringAsFixed(1)}°', // Affiche désormais le dixième de degré pour la précision
'${_rotation.toStringAsFixed(1)}°',
style: const TextStyle(color: Color(0xFF00FF00), fontWeight: FontWeight.bold),
),
],
@@ -167,9 +167,9 @@ class _CropScreenState extends State<CropScreen> {
Expanded(
child: Slider(
value: _rotation,
min: -15.0, // Pivot maximum à gauche bridé à 15°
max: 15.0, // Pivot maximum à droite bridé à 15°
divisions: 300, // 300 divisions pour obtenir un cran ultra-précis tous les 0,1°
min: -15.0,
max: 15.0,
divisions: 300,
label: '${_rotation.toStringAsFixed(1)}°',
activeColor: const Color(0xFF1A73E8),
inactiveColor: Colors.white12,
@@ -182,7 +182,6 @@ class _CropScreenState extends State<CropScreen> {
),
const Icon(Icons.rotate_right, color: Colors.white38, size: 20),
const SizedBox(width: 4),
// Petit bouton Reset rapide pour cette jauge
IconButton(
icon: const Icon(Icons.restart_alt, color: Colors.white54, size: 20),
onPressed: () {
@@ -249,7 +248,7 @@ class _CropScreenState extends State<CropScreen> {
onScaleUpdate: _onScaleUpdate,
child: Stack(
children: [
// 1. L'image brute avec ses transformations
// 1. L'image brute avec ses transformations (Reste calé sur BoxFit.contain d'origine)
Positioned.fill(
child: Center(
child: Transform(
@@ -302,7 +301,6 @@ class _CropScreenState extends State<CropScreen> {
),
child: Stack(
children: [
// Zone visible : Rectangle parfait sans aucun BorderRadius
Center(
child: Container(
width: _cropSize,
@@ -372,8 +370,18 @@ class _CropScreenState extends State<CropScreen> {
setState(() => _isLoading = true);
try {
final cropRect = _calculateCropRect();
final targetCenterX = cropRect.x + cropRect.width / 2;
final targetCenterY = cropRect.y + cropRect.height / 2;
// AJOUT DE LA DECOUPE ET DU REDRESSEMENT GÉOMÉTRIQUE PHYSIQUE
final croppedImagePath = await _cropService.cropToSquare(
widget.imagePath,
cropRect,
rotationDegrees: _rotation, // On transmet la rotation à la jauge haute précision
);
// L'image sortant du service étant désormais un carré physique parfait (1:1),
// le centre de la cible se trouve précisément au milieu (0.5, 0.5)
const targetCenterX = 0.5;
const targetCenterY = 0.5;
if (!mounted) return;
@@ -381,13 +389,13 @@ class _CropScreenState extends State<CropScreen> {
context,
MaterialPageRoute(
builder: (_) => AnalysisScreen(
imagePath: widget.imagePath,
imagePath: croppedImagePath, // On injecte le fichier carré nettoyé
targetType: widget.targetType,
initialCenterX: targetCenterX,
initialCenterY: targetCenterY,
cropScale: _scale,
cropOffset: _offset,
cropRotation: _rotation,
cropScale: 1.0, // Réinitialisé car la texture est déjà recadrée à la bonne échelle
cropOffset: Offset.zero, // Réinitialisé car le fichier est déjà parfaitement centré
cropRotation: 0.0, // Réinitialisé car les pixels ont déjà subi la rotation matricielle
),
),
);
@@ -395,7 +403,7 @@ class _CropScreenState extends State<CropScreen> {
if (mounted) {
setState(() => _isLoading = false);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Erreur: $e'), backgroundColor: AppTheme.errorColor),
SnackBar(content: Text('Erreur de découpe : $e'), backgroundColor: AppTheme.errorColor),
);
}
}
@@ -434,10 +442,10 @@ class _CropScreenState extends State<CropScreen> {
final relCropHeight = _cropSize / scaledHeight;
return CropRect(
x: relCropLeft,
y: relCropTop,
width: relCropWidth,
height: relCropHeight,
x: relCropLeft.clamp(0.0, 1.0),
y: relCropTop.clamp(0.0, 1.0),
width: relCropWidth.clamp(0.0, 1.0),
height: relCropHeight.clamp(0.0, 1.0),
);
}
}