I am using the camera of a mobile phone to get a stream of images, and then analyze the images to find QR codes in them (using Googles ML Kit). Once a QR code has been found, I get the four corner-points of the QR code in the image. I want to overlay this with a rectangle with content in it ("Container" with children in Flutter).
However, usually the camera isn't aligned perfectly to the QR code, so the corner points don't form a rectangle. So in order to overlay the QR code with my rectangle, I need to transform my rectangle so it fits to the corner points.
My question is, given the four corner points, how do I find out the transformation matrix that I need to apply to my content?
CodePudding user response:
this is a quick port of skia's SkMatrix::setPolyToPoly
method - i am not sure about 3 places so check TODO
in the code below:
Matrix4? setPolyToPoly(List<Offset> src, List<Offset> dst) {
Matrix4 tempMap = Matrix4.identity();
Matrix4 result = Matrix4.identity();
if (!_poly4Proc(src, tempMap)) {
return null;
}
// TODO no idea what to do here...
// copyInverse returns some double, but what is it???
// docs say nothing...
// original code checks if matrix can be inverted:
// if (!tempMap.invert(&result)) {
// return false;
// }
result.copyInverse(tempMap);
if (!_poly4Proc(dst, tempMap)) {
return null;
}
return tempMap * result;
}
bool _poly4Proc(List<Offset> src, Matrix4 matrix) {
double a1, a2;
double x0, y0, x1, y1, x2, y2;
x0 = src[2].dx - src[0].dx;
y0 = src[2].dy - src[0].dy;
x1 = src[2].dx - src[1].dx;
y1 = src[2].dy - src[1].dy;
x2 = src[2].dx - src[3].dx;
y2 = src[2].dy - src[3].dy;
/* check if abs(x2) > abs(y2) */
if ( x2 > 0 ? y2 > 0 ? x2 > y2 : x2 > -y2 : y2 > 0 ? -x2 > y2 : x2 < y2) {
double denom = _ieeeFloatDivide(x1 * y2, x2) - y1;
if (_checkForZero(denom)) {
return false;
}
a1 = (((x0 - x1) * y2 / x2) - y0 y1) / denom;
} else {
double denom = x1 - _ieeeFloatDivide(y1 * x2, y2);
if (_checkForZero(denom)) {
return false;
}
a1 = (x0 - x1 - _ieeeFloatDivide((y0 - y1) * x2, y2)) / denom;
}
/* check if abs(x1) > abs(y1) */
if ( x1 > 0 ? y1 > 0 ? x1 > y1 : x1 > -y1 : y1 > 0 ? -x1 > y1 : x1 < y1) {
double denom = y2 - _ieeeFloatDivide(x2 * y1, x1);
if (_checkForZero(denom)) {
return false;
}
a2 = (y0 - y2 - _ieeeFloatDivide((x0 - x2) * y1, x1)) / denom;
} else {
double denom = _ieeeFloatDivide(y2 * x1, y1) - x2;
if (_checkForZero(denom)) {
return false;
}
a2 = (_ieeeFloatDivide((y0 - y2) * x1, y1) - x0 x2) / denom;
}
final array = <double>[
a2 * src[3].dx src[3].dx - src[0].dx, a2 * src[3].dy src[3].dy - src[0].dy, 0, a2,
a1 * src[1].dx src[1].dx - src[0].dx, a1 * src[1].dy src[1].dy - src[0].dy, 0, a1,
0, 0, 1, 0,
src[0].dx, src[0].dy, 0, 1,
];
matrix.copyFromArray(array);
return true;
}
// TODO not sure if its ok
double _ieeeFloatDivide(double d0, double d1) => d0 / d1;
// TODO not sure if its ok
bool _checkForZero(double d) => d == 0;
sample test widget:
class FooPerspective extends StatelessWidget {
static const src = [
Offset(0, 0),
Offset(100, 0),
Offset(100, 100),
Offset(0, 100),
];
static const dst = [
Offset(100, 100),
Offset(200, 120),
Offset(220, 190),
Offset(120, 200),
];
@override
Widget build(BuildContext context) {
return SizedBox.expand(
child: Stack(
children: [
Transform(
transform: setPolyToPoly(src, dst)!,
child: Container(
width: 100,
height: 100,
padding: const EdgeInsets.all(4),
decoration: const BoxDecoration(
color: Colors.orange,
boxShadow: [BoxShadow(spreadRadius: 1, blurRadius: 6, offset: Offset(3, 3))],
),
child: const Text('Cillum non minim officia excepteur in qui.', textScaleFactor: 1.2),
),
),
...dst.map((o) => Positioned(
left: o.dx - 8,
top: o.dy - 8,
width: 16,
height: 16,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(width: 1),
),
),
))
],
),
);
}
}
the result: