fix(camera): synchroniser le ratio de visée 3/4 avec le capteur natif et corriger le décalage du crop
This commit is contained in:
@@ -13,7 +13,7 @@ import '../../data/models/target_type.dart';
|
|||||||
import '../crop/crop_screen.dart';
|
import '../crop/crop_screen.dart';
|
||||||
import '../session/session_provider.dart';
|
import '../session/session_provider.dart';
|
||||||
import 'widgets/image_source_button.dart';
|
import 'widgets/image_source_button.dart';
|
||||||
|
import '../../services/image_crop_service.dart';
|
||||||
class CaptureScreen extends StatefulWidget {
|
class CaptureScreen extends StatefulWidget {
|
||||||
const CaptureScreen({super.key});
|
const CaptureScreen({super.key});
|
||||||
|
|
||||||
@@ -23,10 +23,12 @@ class CaptureScreen extends StatefulWidget {
|
|||||||
|
|
||||||
class _CaptureScreenState extends State<CaptureScreen> with SingleTickerProviderStateMixin {
|
class _CaptureScreenState extends State<CaptureScreen> with SingleTickerProviderStateMixin {
|
||||||
final ImagePicker _picker = ImagePicker();
|
final ImagePicker _picker = ImagePicker();
|
||||||
|
final ImageCropService _cropService = ImageCropService();
|
||||||
final TargetType _selectedType = TargetType.concentric;
|
final TargetType _selectedType = TargetType.concentric;
|
||||||
String? _selectedImagePath;
|
String? _selectedImagePath;
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
|
|
||||||
|
|
||||||
// AJOUT : Variables de gestion du flux caméra en direct
|
// AJOUT : Variables de gestion du flux caméra en direct
|
||||||
CameraController? _cameraController;
|
CameraController? _cameraController;
|
||||||
List<CameraDescription>? _cameras;
|
List<CameraDescription>? _cameras;
|
||||||
@@ -76,11 +78,14 @@ class _CaptureScreenState extends State<CaptureScreen> with SingleTickerProvider
|
|||||||
if (_cameras != null && _cameras!.isNotEmpty) {
|
if (_cameras != null && _cameras!.isNotEmpty) {
|
||||||
_cameraController = CameraController(
|
_cameraController = CameraController(
|
||||||
_cameras![0],
|
_cameras![0],
|
||||||
ResolutionPreset.high,
|
ResolutionPreset.max, // Force le 4:3 natif au maximum du capteur
|
||||||
enableAudio: false,
|
enableAudio: false,
|
||||||
|
imageFormatGroup: ImageFormatGroup.jpeg,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// INITIALISATION UNIQUE ET PROPRE :
|
||||||
await _cameraController!.initialize();
|
await _cameraController!.initialize();
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_isCameraInitialized = true;
|
_isCameraInitialized = true;
|
||||||
_showLiveCamera = true;
|
_showLiveCamera = true;
|
||||||
@@ -215,7 +220,12 @@ class _CaptureScreenState extends State<CaptureScreen> with SingleTickerProvider
|
|||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
// 1. Le flux vidéo de la caméra
|
// 1. Le flux vidéo de la caméra
|
||||||
CameraPreview(_cameraController!),
|
Center(
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 3 / 4, // Aligné sur le format natif du capteur photo
|
||||||
|
child: CameraPreview(_cameraController!),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
// 2. L'ASSISTANT DE CADRAGE VIRTUEL (Avec le grand cadre complet et la petite cible)
|
// 2. L'ASSISTANT DE CADRAGE VIRTUEL (Avec le grand cadre complet et la petite cible)
|
||||||
Center(
|
Center(
|
||||||
@@ -384,15 +394,15 @@ class _CaptureScreenState extends State<CaptureScreen> with SingleTickerProvider
|
|||||||
|
|
||||||
setState(() => _isLoading = true);
|
setState(() => _isLoading = true);
|
||||||
try {
|
try {
|
||||||
// Prise de la photo
|
// 1. On prend la photo brute normale (sans y toucher)
|
||||||
final XFile photo = await _cameraController!.takePicture();
|
final XFile photo = await _cameraController!.takePicture();
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedImagePath = photo.path;
|
_selectedImagePath = photo.path;
|
||||||
_showLiveCamera = false; // Quitte la vue caméra
|
_showLiveCamera = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Redirection automatique vers l'écran de Crop
|
// 2. On l'envoie directement à l'analyse
|
||||||
_analyzeImage();
|
_analyzeImage();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Erreur lors du clic photo: $e');
|
debugPrint('Erreur lors du clic photo: $e');
|
||||||
|
|||||||
@@ -237,7 +237,22 @@ class _CropScreenState extends State<CropScreen> {
|
|||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
_viewportSize = Size(constraints.maxWidth, constraints.maxHeight);
|
_viewportSize = Size(constraints.maxWidth, constraints.maxHeight);
|
||||||
_cropSize = math.min(constraints.maxWidth, constraints.maxHeight) * 0.95;
|
|
||||||
|
// FIX : On calcule d'abord la taille de la photo affichée avant de définir la taille du cadre vert !
|
||||||
|
final imageAspect = _imageSize != null ? _imageSize!.width / _imageSize!.height : 1.0;
|
||||||
|
final viewportAspect = _viewportSize.width / _viewportSize.height;
|
||||||
|
|
||||||
|
double displayWidth, displayHeight;
|
||||||
|
if (imageAspect > viewportAspect) {
|
||||||
|
displayWidth = _viewportSize.width;
|
||||||
|
displayHeight = _viewportSize.width / imageAspect;
|
||||||
|
} else {
|
||||||
|
displayHeight = _viewportSize.height;
|
||||||
|
displayWidth = _viewportSize.height * imageAspect;
|
||||||
|
}
|
||||||
|
|
||||||
|
// On passe de 0.95 à 0.85 pour matcher parfaitement avec l'appareil photo !
|
||||||
|
_cropSize = math.min(displayWidth, displayHeight)* 0.85;
|
||||||
|
|
||||||
if (_scale == 1.0 && _offset == Offset.zero && _rotation == 0.0) {
|
if (_scale == 1.0 && _offset == Offset.zero && _rotation == 0.0) {
|
||||||
_initializeImagePosition();
|
_initializeImagePosition();
|
||||||
@@ -333,6 +348,7 @@ class _CropScreenState extends State<CropScreen> {
|
|||||||
final imageAspect = _imageSize!.width / _imageSize!.height;
|
final imageAspect = _imageSize!.width / _imageSize!.height;
|
||||||
final viewportAspect = _viewportSize.width / _viewportSize.height;
|
final viewportAspect = _viewportSize.width / _viewportSize.height;
|
||||||
|
|
||||||
|
// 1. Calcul strict de la taille de l'image affichée en BoxFit.contain
|
||||||
double displayWidth, displayHeight;
|
double displayWidth, displayHeight;
|
||||||
if (imageAspect > viewportAspect) {
|
if (imageAspect > viewportAspect) {
|
||||||
displayWidth = _viewportSize.width;
|
displayWidth = _viewportSize.width;
|
||||||
@@ -344,11 +360,15 @@ class _CropScreenState extends State<CropScreen> {
|
|||||||
|
|
||||||
final minDisplayDim = math.min(displayWidth, displayHeight);
|
final minDisplayDim = math.min(displayWidth, displayHeight);
|
||||||
|
|
||||||
|
// 2. Calcul du scale initial basé sur la dimension de l'image (et non du viewport)
|
||||||
_scale = widget.initialScale ?? (_cropSize / minDisplayDim);
|
_scale = widget.initialScale ?? (_cropSize / minDisplayDim);
|
||||||
if (_scale < 1.0 && widget.initialScale == null) _scale = 1.0;
|
if (_scale < 1.0 && widget.initialScale == null) _scale = 1.0;
|
||||||
|
|
||||||
|
// 3. Réinitialisation propre de l'offset au centre de la zone d'affichage
|
||||||
if (widget.initialOffset != null) {
|
if (widget.initialOffset != null) {
|
||||||
_offset = widget.initialOffset!;
|
_offset = widget.initialOffset!;
|
||||||
|
} else {
|
||||||
|
_offset = Offset.zero; // Force l'image à se centrer parfaitement sur la croix verte
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -154,4 +154,43 @@ class ImageCropService {
|
|||||||
// Ignorer les erreurs
|
// Ignorer les erreurs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Coupe automatiquement une photo brute de l'appareil photo en carré
|
||||||
|
/// calé sur la position haute du viseur de l'écran.
|
||||||
|
/// Coupe une photo brute selon les proportions exactes du viseur de l'écran.
|
||||||
|
Future<String> cropRawCameraToSquare(String sourcePath) async {
|
||||||
|
final file = File(sourcePath);
|
||||||
|
final bytes = await file.readAsBytes();
|
||||||
|
final originalImage = img.decodeImage(bytes);
|
||||||
|
|
||||||
|
if (originalImage == null) return sourcePath;
|
||||||
|
|
||||||
|
// 1. Déterminer les dimensions réelles (gestion automatique de la rotation du capteur)
|
||||||
|
final int imgW = originalImage.width;
|
||||||
|
final int imgH = originalImage.height;
|
||||||
|
|
||||||
|
// 2. Ton cadre à l'écran fait 85% de la largeur du viewport (0.85).
|
||||||
|
// On calcule la taille du carré par rapport à la plus petite dimension.
|
||||||
|
final int cropSizePixels = (math.min(imgW, imgH) * 0.85).round();
|
||||||
|
|
||||||
|
// 3. Calcul du centre parfait pour le x et le y
|
||||||
|
final int x = ((imgW - cropSizePixels) / 2).round();
|
||||||
|
final int y = ((imgH - cropSizePixels) / 2).round();
|
||||||
|
|
||||||
|
// 4. Découpe physique au pixel près
|
||||||
|
final squareImage = img.copyCrop(
|
||||||
|
originalImage,
|
||||||
|
x: x.clamp(0, imgW - 1),
|
||||||
|
y: y.clamp(0, imgH - 1),
|
||||||
|
width: cropSizePixels,
|
||||||
|
height: cropSizePixels,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 5. Sauvegarde
|
||||||
|
final tempDir = await getTemporaryDirectory();
|
||||||
|
final outputPath = '${tempDir.path}/camera_square_${DateTime.now().millisecondsSinceEpoch}.jpg';
|
||||||
|
await File(outputPath).writeAsBytes(img.encodeJpg(squareImage, quality: 90));
|
||||||
|
|
||||||
|
return outputPath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user