Skip to content

Commit ccb0bba

Browse files
[Infra] - Allow reading multiple QR codes. (Resolves #14) (#15)
1 parent ef8318d commit ccb0bba

File tree

5 files changed

+121
-35
lines changed

5 files changed

+121
-35
lines changed

lib/src/goldens/golden_scenes.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ Future<GoldenCollection> extractGoldenCollectionFromSceneWidgetTree(WidgetTester
7373
GoldenCollection _extractCollectionFromScene(Image sceneImage) {
7474
// Extract the scene metadata from the screenshot.
7575
final qrCode = sceneImage.readQrCode();
76+
if (qrCode == null) {
77+
throw Exception("Couldn't find a QR code in the golden scene image.");
78+
}
7679
final json = JsonDecoder().convert(qrCode.text);
7780
final scene = GoldenSceneMetadata.fromJson(json);
7881

lib/src/qr_codes/qr_code_image_scanning.dart

Lines changed: 76 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,48 +3,89 @@ import 'package:zxing2/qrcode.dart';
33

44
/// QR code extensions on [Image].
55
extension ImageQrScanning on Image {
6-
/// Attempts to find and decode a QR code within this [Image].
7-
QrCode readQrCode() {
8-
final rgbLuminanceSource = RGBLuminanceSource(
9-
width,
10-
height,
11-
convert(numChannels: 4) //
12-
.getBytes(order: ChannelOrder.abgr)
13-
.buffer
14-
.asInt32List(),
15-
);
6+
/// Returns the first QR code found within this [Image], or `null` if no QR code is found.
7+
///
8+
/// If there are multiple QR codes in the image, it returns the first one it finds
9+
/// from top to bottom.
10+
QrCode? readQrCode() {
11+
return _readQrCode(this);
12+
}
1613

17-
try {
18-
final binarizer = HybridBinarizer(rgbLuminanceSource);
19-
final binaryBitmap = BinaryBitmap(binarizer);
20-
return QRCodeReader().decode(binaryBitmap);
21-
} on ReaderException {
22-
// We failed to find a QR code when looking for a dark QR code
23-
// on a light background. Ignore this exception and try to look
24-
// for inverted colors.
25-
}
14+
/// Attempts to find and decode all QR codes within this [Image].
15+
List<QrCode> readAllQrCodes() {
16+
final qrCodes = <QrCode>[];
17+
18+
final image = clone();
19+
20+
// Keep looking for QR codes until we can't find anymore.
21+
bool keepSearching = true;
22+
while (keepSearching) {
23+
final qrcode = _readQrCode(image);
24+
if (qrcode == null) {
25+
keepSearching = false;
26+
continue;
27+
}
28+
29+
qrCodes.add(qrcode);
30+
31+
/// Extract the rectangles from the edge points.
32+
final bottomLeftRectangle = qrcode.resultPoints[0];
33+
final topLeftRectangle = qrcode.resultPoints[1];
34+
final topRightRectangle = qrcode.resultPoints[2];
35+
36+
final top = topRightRectangle.y.truncate();
37+
final left = topLeftRectangle.x.truncate();
38+
final right = topRightRectangle.x.truncate();
39+
final bottom = bottomLeftRectangle.y.truncate();
2640

27-
try {
28-
final binarizer = HybridBinarizer(
29-
InvertedLuminanceSource(
30-
rgbLuminanceSource,
31-
),
41+
// Fill the QRCode region with black color to try to read other QR Codes.
42+
fillRect(
43+
image,
44+
x1: left,
45+
y1: top,
46+
x2: right,
47+
y2: bottom,
48+
color: ColorRgb8(0, 0, 0),
3249
);
33-
final binaryBitmap = BinaryBitmap(binarizer);
34-
return QRCodeReader().decode(binaryBitmap);
35-
} on ReaderException catch (exception) {
36-
throw CouldNotReadQrCodeException(exception);
3750
}
51+
52+
return qrCodes;
3853
}
3954
}
4055

41-
typedef QrCode = Result;
42-
43-
class CouldNotReadQrCodeException implements Exception {
44-
const CouldNotReadQrCodeException([this.rootCause]);
56+
QrCode? _readQrCode(Image image) {
57+
final rgbLuminanceSource = RGBLuminanceSource(
58+
image.width,
59+
image.height,
60+
image
61+
.convert(numChannels: 4) //
62+
.getBytes(order: ChannelOrder.abgr)
63+
.buffer
64+
.asInt32List(),
65+
);
4566

46-
final Exception? rootCause;
67+
try {
68+
final binarizer = HybridBinarizer(rgbLuminanceSource);
69+
final binaryBitmap = BinaryBitmap(binarizer);
70+
return QRCodeReader().decode(binaryBitmap);
71+
} on ReaderException {
72+
// We failed to find a QR code when looking for a dark QR code
73+
// on a light background. Ignore this exception and try to look
74+
// for inverted colors.
75+
}
4776

48-
@override
49-
String toString() => "CouldNotReadQrCodeException";
77+
try {
78+
final binarizer = HybridBinarizer(
79+
InvertedLuminanceSource(
80+
rgbLuminanceSource,
81+
),
82+
);
83+
final binaryBitmap = BinaryBitmap(binarizer);
84+
return QRCodeReader().decode(binaryBitmap);
85+
} on ReaderException {
86+
// We didn't find a QR code in the image.
87+
return null;
88+
}
5089
}
90+
91+
typedef QrCode = Result;

test/qr_code/multiple_qrcodes.png

36.3 KB
Loading

test/qr_code/qr_code_test.dart

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import 'dart:io';
2+
3+
import 'package:flutter_test/flutter_test.dart';
4+
import 'package:flutter_test_goldens/flutter_test_goldens.dart';
5+
6+
import 'package:image/image.dart' as img;
7+
8+
void main() {
9+
group('QR Code scanning >', () {
10+
test('reads single QR code from an image', () async {
11+
final image = img.decodePng(File('test/qr_code/single_qrcode.png').readAsBytesSync())!;
12+
13+
final qrCode = image.readQrCode();
14+
15+
expect(qrCode, isNotNull);
16+
expect(qrCode!.text, 'A QR Code');
17+
});
18+
19+
test('reads single QR code from an image with multiple QR codes', () async {
20+
final image = img.decodePng(File('test/qr_code/multiple_qrcodes.png').readAsBytesSync())!;
21+
22+
final qrCode = image.readQrCode();
23+
24+
// In top to bottom order, the third QR code is the first one.
25+
expect(qrCode, isNotNull);
26+
expect(qrCode!.text, 'Third QR Code');
27+
});
28+
29+
test('reads multiple QR codes from a single image', () async {
30+
final image = img.decodePng(File('test/qr_code/multiple_qrcodes.png').readAsBytesSync())!;
31+
32+
final qrCodes = image.readAllQrCodes();
33+
34+
expect(qrCodes.length, 3);
35+
36+
// In top to bottom order, the third QR code is the first one.
37+
expect(qrCodes[0].text, 'Third QR Code');
38+
expect(qrCodes[1].text, 'First QR Code');
39+
expect(qrCodes[2].text, 'Second QR Code');
40+
});
41+
});
42+
}

test/qr_code/single_qrcode.png

1.51 KB
Loading

0 commit comments

Comments
 (0)