diff --git a/lib/features/crop/crop_screen.dart b/lib/features/crop/crop_screen.dart index 3aceec41..e1e62116 100644 --- a/lib/features/crop/crop_screen.dart +++ b/lib/features/crop/crop_screen.dart @@ -407,6 +407,9 @@ class _CropScreenState extends State { offsetDy: _offset.dy, displayPerSourcePx: displayPerSourcePx, cropSizeDisplay: _cropSize, + // Le zoom sert UNIQUEMENT à viser le bon point (mapping du décalage) ; + // il n'agrandit pas le rendu de sortie. + zoomScale: _scale, rotationDegrees: _rotation, ); diff --git a/lib/services/image_crop_service.dart b/lib/services/image_crop_service.dart index bb96820b..5639df8d 100644 --- a/lib/services/image_crop_service.dart +++ b/lib/services/image_crop_service.dart @@ -105,6 +105,7 @@ class _ViewportCropParams { final double offsetDy; final double displayPerSourcePx; // facteur affichage/source (BoxFit.contain) final double cropSizeDisplay; // côté de la fenêtre de visée, en pixels écran + final double zoomScale; // zoom utilisateur : sert UNIQUEMENT au pointage final double rotationDegrees; final int outputSize; final String outputPath; @@ -115,6 +116,7 @@ class _ViewportCropParams { required this.offsetDy, required this.displayPerSourcePx, required this.cropSizeDisplay, + required this.zoomScale, required this.rotationDegrees, required this.outputSize, required this.outputPath, @@ -143,14 +145,19 @@ void _viewportCropIsolateEntry(_ViewportCropParams p) { } final double f = p.displayPerSourcePx; - // Côté de la fenêtre de visée, exprimé en pixels source (rotation = même échelle) + // Côté de la fenêtre de visée en pixels source. On utilise f SEUL (pas le + // zoom) → le champ de vision capturé est toujours celui de l'image non + // zoomée : le zoom n'est donc PAS pris en compte dans le rendu de sortie. final int side = math.max(1, (p.cropSizeDisplay / f).round()); - // Centre de la fenêtre de visée, en pixels de l'image (rotated) : - // le centre de l'image est affiché à (centre_viewport + offset), la fenêtre - // est centrée sur le viewport → décalage de -offset/f par rapport au centre. - final double cropCenterX = rotated.width / 2 - p.offsetDx / f; - final double cropCenterY = rotated.height / 2 - p.offsetDy / f; + // Centre de la fenêtre de visée, en pixels de l'image (rotated). + // La translation écran s'applique APRÈS le zoom, donc un déplacement écran + // `offset` vaut `offset / (f * zoom)` pixels source. On inclut donc le zoom + // ICI (uniquement pour le pointage) afin que la croix vise le même point + // quel que soit le niveau de zoom. + final double fz = f * p.zoomScale; + final double cropCenterX = rotated.width / 2 - p.offsetDx / fz; + final double cropCenterY = rotated.height / 2 - p.offsetDy / fz; final int srcX = (cropCenterX - side / 2).round(); final int srcY = (cropCenterY - side / 2).round(); @@ -212,6 +219,7 @@ class ImageCropService { required double offsetDy, required double displayPerSourcePx, required double cropSizeDisplay, + double zoomScale = 1.0, double rotationDegrees = 0.0, int outputSize = maxOutputSize, }) async { @@ -224,6 +232,7 @@ class ImageCropService { offsetDy: offsetDy, displayPerSourcePx: displayPerSourcePx, cropSizeDisplay: cropSizeDisplay, + zoomScale: zoomScale <= 0 ? 1.0 : zoomScale, rotationDegrees: rotationDegrees, outputSize: outputSize, outputPath: outputPath,