Skip to content

Commit 44d10ee

Browse files
committed
fix: Fix the issue of incorrect QR code position detection when Boxfit is set to cover and update test example.
1 parent 665f641 commit 44d10ee

File tree

2 files changed

+171
-74
lines changed

2 files changed

+171
-74
lines changed

example/lib/barcode_scanner_window.dart

Lines changed: 150 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,46 +19,165 @@ class _BarcodeScannerWithScanWindowState
1919
);
2020

2121
// TODO: Fix BoxFit.fill & BoxFit.fitHeight
22-
final boxFit = BoxFit.contain;
22+
BoxFit boxFit = BoxFit.contain;
23+
double containerWidth = 300;
24+
double containerHeight = 600;
2325

2426
@override
2527
Widget build(BuildContext context) {
26-
final scanWindow = Rect.fromCenter(
27-
center: MediaQuery.sizeOf(context).center(const Offset(0, -100)),
28-
width: 300,
29-
height: 200,
28+
final scanWindowWidth = containerWidth * 0.8;
29+
final scanWindowHeight = containerHeight * 0.2;
30+
31+
final scanWindow = Rect.fromLTWH(
32+
(containerWidth - scanWindowWidth) / 2,
33+
(containerHeight - scanWindowHeight) / 3,
34+
scanWindowWidth,
35+
scanWindowHeight,
3036
);
3137

3238
return Scaffold(
3339
appBar: AppBar(title: const Text('With Scan window')),
3440
backgroundColor: Colors.black,
35-
body: Stack(
36-
fit: StackFit.expand,
37-
children: [
38-
MobileScanner(
39-
fit: boxFit,
40-
scanWindow: scanWindow,
41-
controller: controller,
42-
errorBuilder: (context, error) {
43-
return ScannerErrorWidget(error: error);
44-
},
45-
),
46-
BarcodeOverlay(controller: controller, boxFit: boxFit),
47-
ScanWindowOverlay(
48-
scanWindow: scanWindow,
49-
controller: controller,
50-
),
51-
Align(
52-
alignment: Alignment.bottomCenter,
53-
child: Container(
54-
alignment: Alignment.center,
55-
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
56-
height: 100,
57-
color: const Color.fromRGBO(0, 0, 0, 0.4),
58-
child: ScannedBarcodeLabel(barcodes: controller.barcodes),
41+
body: Container(
42+
height: MediaQuery.sizeOf(context).height,
43+
width: MediaQuery.sizeOf(context).width,
44+
color: Colors.blueGrey,
45+
child: Stack(
46+
children: [
47+
const Text(
48+
"Background",
49+
style: TextStyle(
50+
color: Colors.white,
51+
fontSize: 30,
52+
fontWeight: FontWeight.bold),
53+
),
54+
Center(
55+
child: Container(
56+
width: containerWidth,
57+
height: containerHeight,
58+
color: Colors.red,
59+
child: Stack(
60+
fit: StackFit.expand,
61+
children: [
62+
MobileScanner(
63+
fit: boxFit,
64+
scanWindow: scanWindow,
65+
controller: controller,
66+
errorBuilder: (context, error) {
67+
return ScannerErrorWidget(error: error);
68+
},
69+
),
70+
BarcodeOverlay(controller: controller, boxFit: boxFit),
71+
ScanWindowOverlay(
72+
scanWindow: scanWindow,
73+
controller: controller,
74+
),
75+
Align(
76+
alignment: Alignment.bottomCenter,
77+
child: Container(
78+
alignment: Alignment.center,
79+
padding: const EdgeInsets.symmetric(
80+
horizontal: 16, vertical: 8),
81+
height: 100,
82+
color: const Color.fromRGBO(0, 0, 0, 0.4),
83+
child:
84+
ScannedBarcodeLabel(barcodes: controller.barcodes),
85+
),
86+
),
87+
const Text(
88+
"Camera preview",
89+
style: TextStyle(
90+
color: Colors.white,
91+
fontSize: 30,
92+
fontWeight: FontWeight.bold),
93+
),
94+
Positioned(
95+
bottom: 100,
96+
width: containerWidth,
97+
child: Wrap(
98+
spacing: 0.5,
99+
runSpacing: 3.0,
100+
alignment: WrapAlignment.center,
101+
children: <Widget>[
102+
SizedBox(
103+
height: 30,
104+
child: ElevatedButton(
105+
onPressed: () {
106+
setState(() {
107+
boxFit = BoxFit.fill;
108+
});
109+
},
110+
child: const Text("fill"),
111+
)),
112+
SizedBox(
113+
height: 30,
114+
child: ElevatedButton(
115+
onPressed: () {
116+
setState(() {
117+
boxFit = BoxFit.contain;
118+
});
119+
},
120+
child: const Text("contain"),
121+
)),
122+
SizedBox(
123+
height: 30,
124+
child: ElevatedButton(
125+
onPressed: () {
126+
setState(() {
127+
boxFit = BoxFit.cover;
128+
});
129+
},
130+
child: const Text("cover"),
131+
)),
132+
SizedBox(
133+
height: 30,
134+
child: ElevatedButton(
135+
onPressed: () {
136+
setState(() {
137+
boxFit = BoxFit.fitWidth;
138+
});
139+
},
140+
child: const Text("fitWidth"),
141+
)),
142+
SizedBox(
143+
height: 30,
144+
child: ElevatedButton(
145+
onPressed: () {
146+
setState(() {
147+
boxFit = BoxFit.fitHeight;
148+
});
149+
},
150+
child: const Text("fitHeight"),
151+
)),
152+
SizedBox(
153+
height: 30,
154+
child: ElevatedButton(
155+
onPressed: () {
156+
setState(() {
157+
boxFit = BoxFit.none;
158+
});
159+
},
160+
child: const Text("none"),
161+
)),
162+
SizedBox(
163+
height: 30,
164+
child: ElevatedButton(
165+
onPressed: () {
166+
setState(() {
167+
boxFit = BoxFit.scaleDown;
168+
});
169+
},
170+
child: const Text("scaleDown"),
171+
)),
172+
],
173+
),
174+
)
175+
],
176+
),
177+
),
59178
),
60-
),
61-
],
179+
],
180+
),
62181
),
63182
);
64183
}

lib/src/overlay/barcode_painter.dart

Lines changed: 21 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import "dart:math" as math;
1+
import 'dart:math' as math;
22
import 'package:collection/collection.dart';
33
import 'package:flutter/material.dart';
44

@@ -41,31 +41,20 @@ class BarcodePainter extends CustomPainter {
4141
return;
4242
}
4343

44-
ScalingRatios ratio = calculateBoxFitRatio(boxFit, cameraPreviewSize, size);
44+
final ratios = calculateBoxFitRatio(boxFit, cameraPreviewSize, size);
4545
// final adjustedSize = applyBoxFit(boxFit, cameraPreviewSize, size);
4646

47-
double horizontalPadding =
48-
((cameraPreviewSize.width * ratio.widthRatio - size.width) / 2);
49-
double verticalPadding =
50-
((cameraPreviewSize.height * ratio.heightRatio - size.height) / 2);
47+
final double horizontalPadding =
48+
(cameraPreviewSize.width * ratios.widthRatio - size.width) / 2;
49+
final double verticalPadding =
50+
(cameraPreviewSize.height * ratios.heightRatio - size.height) / 2;
5151

5252
final List<Offset> adjustedOffset = [
53-
Offset(
54-
(barcodeCorners[0].dx * ratio.widthRatio - horizontalPadding),
55-
(barcodeCorners[0].dy * ratio.heightRatio - verticalPadding),
56-
),
57-
Offset(
58-
(barcodeCorners[1].dx * ratio.widthRatio - horizontalPadding),
59-
(barcodeCorners[1].dy * ratio.heightRatio - verticalPadding),
60-
),
61-
Offset(
62-
(barcodeCorners[2].dx * ratio.widthRatio - horizontalPadding),
63-
(barcodeCorners[2].dy * ratio.heightRatio - verticalPadding),
64-
),
65-
Offset(
66-
(barcodeCorners[3].dx * ratio.widthRatio - horizontalPadding),
67-
(barcodeCorners[3].dy * ratio.heightRatio - verticalPadding),
68-
),
53+
for (final offset in barcodeCorners)
54+
Offset(
55+
offset.dx * ratios.widthRatio - horizontalPadding,
56+
offset.dy * ratios.heightRatio - verticalPadding,
57+
),
6958
];
7059

7160
final cutoutPath = Path()..addPolygon(adjustedOffset, true);
@@ -90,28 +79,17 @@ class BarcodePainter extends CustomPainter {
9079
}
9180
}
9281

93-
class ScalingRatios {
94-
final double widthRatio;
95-
final double heightRatio;
96-
97-
ScalingRatios(this.widthRatio, this.heightRatio);
98-
99-
@override
100-
String toString() =>
101-
'ScalingRatios(widthRatio: $widthRatio, heightRatio: $heightRatio)';
102-
}
103-
10482
/// Calculate the scaling ratios for width and height to fit the small box (cameraPreviewSize)
10583
/// into the large box (size) based on the specified BoxFit mode.
106-
/// Returns a ScalingRatios object containing the width and height scaling ratios.
107-
ScalingRatios calculateBoxFitRatio(
84+
/// Returns a record containing the width and height scaling ratios.
85+
({double widthRatio, double heightRatio}) calculateBoxFitRatio(
10886
BoxFit boxFit, Size cameraPreviewSize, Size size) {
10987
// If the width or height of cameraPreviewSize or size is 0, return (1.0, 1.0) (no scaling)
11088
if (cameraPreviewSize.width <= 0 ||
11189
cameraPreviewSize.height <= 0 ||
11290
size.width <= 0 ||
11391
size.height <= 0) {
114-
return ScalingRatios(1.0, 1.0);
92+
return (widthRatio: 1.0, heightRatio: 1.0);
11593
}
11694

11795
// Calculate the scaling ratios for width and height
@@ -121,33 +99,33 @@ ScalingRatios calculateBoxFitRatio(
12199
switch (boxFit) {
122100
case BoxFit.fill:
123101
// Stretch to fill the large box without maintaining aspect ratio
124-
return ScalingRatios(widthRatio, heightRatio);
102+
return (widthRatio: widthRatio, heightRatio: heightRatio);
125103

126104
case BoxFit.contain:
127105
// Maintain aspect ratio, ensure the content fits entirely within the large box
128106
final ratio = math.min(widthRatio, heightRatio);
129-
return ScalingRatios(ratio, ratio);
107+
return (widthRatio: ratio, heightRatio: ratio);
130108

131109
case BoxFit.cover:
132110
// Maintain aspect ratio, ensure the content fully covers the large box
133111
final ratio = math.max(widthRatio, heightRatio);
134-
return ScalingRatios(ratio, ratio);
112+
return (widthRatio: ratio, heightRatio: ratio);
135113

136114
case BoxFit.fitWidth:
137115
// Maintain aspect ratio, ensure the width matches the large box
138-
return ScalingRatios(widthRatio, widthRatio);
116+
return (widthRatio: widthRatio, heightRatio: widthRatio);
139117

140118
case BoxFit.fitHeight:
141119
// Maintain aspect ratio, ensure the height matches the large box
142-
return ScalingRatios(heightRatio, heightRatio);
120+
return (widthRatio: heightRatio, heightRatio: heightRatio);
143121

144122
case BoxFit.none:
145123
// No scaling
146-
return ScalingRatios(1.0, 1.0);
124+
return (widthRatio: 1.0, heightRatio: 1.0);
147125

148126
case BoxFit.scaleDown:
149127
// If the content is larger than the large box, scale down to fit; otherwise, no scaling
150128
final ratio = math.min(1.0, math.min(widthRatio, heightRatio));
151-
return ScalingRatios(ratio, ratio);
129+
return (widthRatio: ratio, heightRatio: ratio);
152130
}
153131
}

0 commit comments

Comments
 (0)