mobile_scanner icon indicating copy to clipboard operation
mobile_scanner copied to clipboard

How to draw the rectangle shapes of all detected barcodes on screen?

Open Hilbert2048 opened this issue 1 year ago • 4 comments

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

Hilbert2048 avatar Apr 17 '23 03:04 Hilbert2048

image

I want to implement something like this when scanning multiple barcodes.

Hilbert2048 avatar Apr 17 '23 03:04 Hilbert2048

Same question. The corner offsets seem to be scaled from some other image size, not the size the MobileScanner widget on screen.

komaxx avatar Oct 05 '23 13:10 komaxx

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)

IcarusSosie avatar Nov 28 '23 09:11 IcarusSosie