mobile_scanner
mobile_scanner copied to clipboard
How to draw the rectangle shapes of all detected barcodes on screen?
I found some draft codes in mobile_scanner_overlay.dart, but it is incomplete, how to draws a shape based on the corners point of each Barcode? I try, but it seems that the coordinate of these points are incorrect, need some help.
thanks
![image](https://user-images.githubusercontent.com/125776109/232373884-cf22480b-82df-43e1-9782-883233476337.png)
I want to implement something like this when scanning multiple barcodes.
Same question. The corner offsets seem to be scaled from some other image size, not the size the MobileScanner
widget on screen.
Hey,
I had the same issue but fixed it by offsetting all shapes drawn on the overlay. It seems the list of corners returned are meant to be applied on top of the image provided in the BarcodeCapture
argument of the onDetect
callback. The image size depends on the size provided to MobileScannerController.cameraResolution
, which by default is 480x640.
I ended up specifying the size myself in case of changes in later versions.
If your MobileScanner
displays in portrait mode, you can then obtain the width constraint, subtract 480, and divide by 2 to obtain the x offset needed to display the shape properly on a MobileScanner(fit: BoxFit.cover)
.
Here's the working code in my case :
import 'dart:math';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
@RoutePage()
class QRPage extends StatefulWidget {
const QRPage({super.key});
@override
State<QRPage> createState() => _QRPageState();
}
class _QRPageState extends State<QRPage> with TickerProviderStateMixin {
List<Widget> detects = <Widget>[];
static const Size cameraResolution = Size(480, 640);
@override
Widget build(BuildContext context) {
return SafeArea(
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return MobileScanner(
onDetect: (BarcodeCapture capture) {
final Iterable<Widget> Function(Barcode) getOverlayElements =
_getOverlayElements(
(constraints.biggest.width - cameraResolution.width) / 2,
);
setState(() {
detects = capture.barcodes
.expand<Widget>(getOverlayElements)
.toList();
});
},
overlay: Stack(children: detects),
controller: MobileScannerController(
cameraResolution: cameraResolution,
),
);
},
),
);
}
Iterable<Widget> Function(Barcode code) _getOverlayElements(double offsetX) {
return (Barcode code) {
final Rect boundingBox = code.corners.boundingBox;
return <Widget?>[
AnimatedPositioned(
key: ValueKey<String?>(code.rawValue),
left: boundingBox.left + offsetX,
top: boundingBox.top,
duration: Durations.short1,
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: Colors.white70,
width: 5,
strokeAlign: BorderSide.strokeAlignOutside,
),
),
child:
SizedBox.fromSize(size: boundingBox.size, child: Container()),
),
),
].whereType<Widget>();
};
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(IterableProperty<Widget>('detects', detects));
}
}
/// adds the ability to get the smallest non-rotated enclosing rectangle for a
/// list of offsets.
extension GetBoundingBox on List<Offset> {
/// biggest rectangle enclosing all offsets.
Rect get boundingBox {
return Rect.fromPoints(
Offset(_reduceX(min), _reduceY(min)),
Offset(_reduceX(max), _reduceY(max)),
);
}
double _reduceX(
double Function(double a, double b) reducer,
) =>
map<double>((Offset o) => o.dx).reduce(reducer);
double _reduceY(
double Function(double a, double b) reducer,
) =>
map<double>((Offset o) => o.dy).reduce(reducer);
}
This seems to display properly for my use-case. (The animation code is not required, it just makes the live detection less jarring)