|
|
@@ -0,0 +1,109 @@
|
|
|
+import 'package:body_detection/models/pose.dart';
|
|
|
+import 'package:body_detection/models/pose_landmark.dart';
|
|
|
+import 'package:body_detection/models/pose_landmark_type.dart';
|
|
|
+import 'package:flutter/widgets.dart';
|
|
|
+
|
|
|
+class PosePainter extends CustomPainter {
|
|
|
+ final Pose? pose;
|
|
|
+ final Size imageSize;
|
|
|
+ PosePainter({required this.pose, required this.imageSize, Key? key});
|
|
|
+
|
|
|
+ final pointPaint = Paint()..color = const Color.fromRGBO(255, 255, 255, 0.8);
|
|
|
+ final leftPointPaint = Paint()..color = const Color.fromRGBO(223, 157, 80, 1);
|
|
|
+ final rightPointPaint = Paint()..color = const Color.fromRGBO(100, 208, 218, 1);
|
|
|
+ final linePaint = Paint()
|
|
|
+ ..color = const Color.fromARGB(228, 0, 0, 0)
|
|
|
+ ..strokeWidth = 3;
|
|
|
+ final maskPaint = Paint()..colorFilter = const ColorFilter.mode(Color.fromRGBO(0, 0, 255, 0.5), BlendMode.srcOut);
|
|
|
+
|
|
|
+ @override
|
|
|
+ void paint(Canvas canvas, Size size) {
|
|
|
+ if (pose == null) return;
|
|
|
+
|
|
|
+ final double hRatio = imageSize.width == 0 ? 1 : size.width / imageSize.width;
|
|
|
+ final double vRatio = imageSize.height == 0 ? 1 : size.height / imageSize.height;
|
|
|
+
|
|
|
+ offsetForPart(PoseLandmark part) => Offset(part.position.x * hRatio, part.position.y * vRatio);
|
|
|
+
|
|
|
+ // Landmark connections
|
|
|
+ final landmarksByType = {for (final it in pose!.landmarks) it.type: it};
|
|
|
+ for (final connection in _connections) {
|
|
|
+ final point1 = offsetForPart(landmarksByType[connection[0]]!);
|
|
|
+ final point2 = offsetForPart(landmarksByType[connection[1]]!);
|
|
|
+ canvas.drawLine(point1, point2, linePaint);
|
|
|
+ }
|
|
|
+
|
|
|
+ for (final part in pose!.landmarks) {
|
|
|
+ // Landmark points
|
|
|
+ canvas.drawCircle(offsetForPart(part), 5, pointPaint);
|
|
|
+ if (part.type.isLeftSide) {
|
|
|
+ canvas.drawCircle(offsetForPart(part), 3, leftPointPaint);
|
|
|
+ } else if (part.type.isRightSide) {
|
|
|
+ canvas.drawCircle(offsetForPart(part), 3, rightPointPaint);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Landmark labels
|
|
|
+ TextSpan span = TextSpan(
|
|
|
+ text: part.type.toString().substring(16),
|
|
|
+ style: const TextStyle(
|
|
|
+ color: Color.fromRGBO(0, 128, 255, 1),
|
|
|
+ fontSize: 10,
|
|
|
+ shadows: [
|
|
|
+ Shadow(
|
|
|
+ color: Color.fromRGBO(255, 255, 255, 1),
|
|
|
+ offset: Offset(1, 1),
|
|
|
+ blurRadius: 1,
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ TextPainter tp = TextPainter(text: span, textAlign: TextAlign.left);
|
|
|
+ tp.textDirection = TextDirection.ltr;
|
|
|
+ tp.layout();
|
|
|
+ tp.paint(canvas, offsetForPart(part));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ bool shouldRepaint(PosePainter oldDelegate) {
|
|
|
+ return oldDelegate.pose != pose || oldDelegate.imageSize != imageSize;
|
|
|
+ }
|
|
|
+
|
|
|
+ static const List<List<PoseLandmarkType>> _connections = [
|
|
|
+ [PoseLandmarkType.leftEar, PoseLandmarkType.leftEyeOuter],
|
|
|
+ [PoseLandmarkType.leftEyeOuter, PoseLandmarkType.leftEye],
|
|
|
+ [PoseLandmarkType.leftEye, PoseLandmarkType.leftEyeInner],
|
|
|
+ [PoseLandmarkType.leftEyeInner, PoseLandmarkType.nose],
|
|
|
+ [PoseLandmarkType.nose, PoseLandmarkType.rightEyeInner],
|
|
|
+ [PoseLandmarkType.rightEyeInner, PoseLandmarkType.rightEye],
|
|
|
+ [PoseLandmarkType.rightEye, PoseLandmarkType.rightEyeOuter],
|
|
|
+ [PoseLandmarkType.rightEyeOuter, PoseLandmarkType.rightEar],
|
|
|
+ [PoseLandmarkType.mouthLeft, PoseLandmarkType.mouthRight],
|
|
|
+ [PoseLandmarkType.leftShoulder, PoseLandmarkType.rightShoulder],
|
|
|
+ [PoseLandmarkType.leftShoulder, PoseLandmarkType.leftHip],
|
|
|
+ [PoseLandmarkType.rightShoulder, PoseLandmarkType.rightHip],
|
|
|
+ [PoseLandmarkType.rightShoulder, PoseLandmarkType.rightElbow],
|
|
|
+ [PoseLandmarkType.rightWrist, PoseLandmarkType.rightElbow],
|
|
|
+ [PoseLandmarkType.rightWrist, PoseLandmarkType.rightThumb],
|
|
|
+ [PoseLandmarkType.rightWrist, PoseLandmarkType.rightIndexFinger],
|
|
|
+ [PoseLandmarkType.rightWrist, PoseLandmarkType.rightPinkyFinger],
|
|
|
+ [PoseLandmarkType.leftHip, PoseLandmarkType.rightHip],
|
|
|
+ [PoseLandmarkType.leftHip, PoseLandmarkType.leftKnee],
|
|
|
+ [PoseLandmarkType.rightHip, PoseLandmarkType.rightKnee],
|
|
|
+ [PoseLandmarkType.rightKnee, PoseLandmarkType.rightAnkle],
|
|
|
+ [PoseLandmarkType.leftKnee, PoseLandmarkType.leftAnkle],
|
|
|
+ [PoseLandmarkType.leftElbow, PoseLandmarkType.leftShoulder],
|
|
|
+ [PoseLandmarkType.leftWrist, PoseLandmarkType.leftElbow],
|
|
|
+ [PoseLandmarkType.leftWrist, PoseLandmarkType.leftThumb],
|
|
|
+ [PoseLandmarkType.leftWrist, PoseLandmarkType.leftIndexFinger],
|
|
|
+ [PoseLandmarkType.leftWrist, PoseLandmarkType.leftPinkyFinger],
|
|
|
+ [PoseLandmarkType.leftAnkle, PoseLandmarkType.leftHeel],
|
|
|
+ [PoseLandmarkType.leftAnkle, PoseLandmarkType.leftToe],
|
|
|
+ [PoseLandmarkType.rightAnkle, PoseLandmarkType.rightHeel],
|
|
|
+ [PoseLandmarkType.rightAnkle, PoseLandmarkType.rightToe],
|
|
|
+ [PoseLandmarkType.rightHeel, PoseLandmarkType.rightToe],
|
|
|
+ [PoseLandmarkType.leftHeel, PoseLandmarkType.leftToe],
|
|
|
+ [PoseLandmarkType.rightIndexFinger, PoseLandmarkType.rightPinkyFinger],
|
|
|
+ [PoseLandmarkType.leftIndexFinger, PoseLandmarkType.leftPinkyFinger],
|
|
|
+ ];
|
|
|
+}
|