import 'dart:math' as math; import 'dart:ui'; /// Shared capture guide geometry (same as the on-screen shooting frame). class GuideFrame { static const double aspect = 63 / 88; static const double maxHeightFraction = 0.62; static const double maxWidthFraction = 0.78; static const double centerYFraction = 0.46; /// Guide rect inside a viewport (preview overlay coordinates). static Rect inViewport(Size size) { final maxH = size.height * maxHeightFraction; final maxW = size.width * maxWidthFraction; var h = maxH; var w = h * aspect; if (w > maxW) { w = maxW; h = w / aspect; } return Rect.fromCenter( center: Offset(size.width / 2, size.height * centerYFraction), width: w, height: h, ); } /// Map the viewport guide into image pixels when the image is shown with /// [BoxFit.cover] in [viewport] (camera preview). static Rect inCoverImage({ required Size viewport, required Size imageSize, }) { final guide = inViewport(viewport); final scale = math.max( viewport.width / imageSize.width, viewport.height / imageSize.height, ); final dispW = imageSize.width * scale; final dispH = imageSize.height * scale; final ox = (viewport.width - dispW) / 2; final oy = (viewport.height - dispH) / 2; final mapped = Rect.fromLTRB( (guide.left - ox) / scale, (guide.top - oy) / scale, (guide.right - ox) / scale, (guide.bottom - oy) / scale, ); return mapped.intersect( Rect.fromLTWH(0, 0, imageSize.width, imageSize.height), ); } /// Guide applied directly in image space (gallery / no viewport). static Rect inImage(Size imageSize) => inViewport(imageSize); }