Browse Source

优化全屏的30s心电图像

gavin.chen 1 year ago
parent
commit
4c94012b4c

+ 87 - 20
lib/pages/medical/widgets/ecg_view/controller.dart

@@ -1,8 +1,12 @@
 import 'dart:async';
+import 'dart:convert';
+import 'dart:ui';
 
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
 import 'package:get/get.dart';
 import 'package:vitalapp/architecture/utils/prompt_box.dart';
-
+import 'dart:ui' as ui;
 import 'index.dart';
 
 class EcgViewController extends GetxController {
@@ -57,6 +61,52 @@ class EcgViewController extends GetxController {
     }
   }
 
+  /// 打开全屏心电图弹窗
+  void openFullScreenDialog() {
+    print("当前点总数为:${allPoints.length}");
+    if (allPoints.length < dataPerSecond * 30) {
+      PromptBox.toast("未完成检测,数据量不足");
+      return;
+    }
+    Get.dialog(
+      const FullScreenEcgDataDialog(),
+    );
+  }
+
+  // 重置
+  void reset() {
+    allPoints.clear();
+    newPointsToDraw.clear();
+    oldPointsToDraw.clear();
+    startTime = DateTime.now().millisecondsSinceEpoch;
+    currentDataIndex = 0;
+    timer.cancel();
+    isPaused = false;
+    update(['ecg_view']);
+  }
+
+  /// 获取完整心电图的base64(带base64头)
+  Future<String> getFullDataImageBase64() async {
+    if (allPoints.length < dataPerSecond * 30) {
+      PromptBox.toast("未完成检测,数据量不足");
+      return "";
+    }
+    final painter = EcgPainterForAll(
+      allPoints: allPoints,
+      yMax: 600,
+    );
+    final bgPainter = GridBackgroundPainterForAll();
+    const size = Size(5000, 650);
+    // 使用离屏Canvas绘制
+    final Uint8List? bytes =
+        await _capturePainterToImage(painter, bgPainter, size);
+    if (bytes == null) {
+      return "";
+    } else {
+      return _convertToBase64Url(bytes);
+    }
+  }
+
   /// 开启定时器,每隔一定时间添加一次数据,并且更新UI
   void _startTimer() {
     timer = Timer.periodic(
@@ -94,33 +144,50 @@ class EcgViewController extends GetxController {
       // print(
       //     "update newPointsToDraw: ${currentDataIndex} ${needDataCount} ${newPointsToDraw.length} --currentPeriod ${currentPeriod}");
     } catch (e) {
-      print(e);
       timer.cancel();
       isPaused = true;
     }
     update(['ecg_view']);
   }
 
-  void openFullScreenDialog() {
-    print("当前点总数为:${allPoints.length}");
-    if (allPoints.length < dataPerSecond * 30) {
-      PromptBox.toast("未完成检测,数据量不足");
-      return;
-    }
-    Get.dialog(
-      const FullScreenEcgDataDialog(),
-    );
+  /// 将字节数组转换为base64
+  String _convertToBase64Url(Uint8List imageData) {
+    String base64Image = base64Encode(imageData);
+    String base64Url = 'data:image/png;base64,$base64Image';
+    return base64Url;
   }
 
-  void reset() {
-    allPoints.clear();
-    newPointsToDraw.clear();
-    oldPointsToDraw.clear();
-    startTime = DateTime.now().millisecondsSinceEpoch;
-    currentDataIndex = 0;
-    timer.cancel();
-    isPaused = false;
-    update(['ecg_view']);
+  /// 将CustomPainter绘制的内容转换为图片
+  Future<Uint8List?> _capturePainterToImage(
+      CustomPainter painter, CustomPainter bgPainter, Size size) async {
+    final bounds = Offset.zero & size;
+    final picture = PictureRecorder();
+    final pictureCanvas = Canvas(picture);
+
+    // 给Canvas设置绘制范围
+    pictureCanvas.clipRect(bounds);
+
+    // 在Canvas上进行绘制
+    bgPainter.paint(pictureCanvas, size);
+    painter.paint(pictureCanvas, size);
+
+    /// 绘制一圈边框
+    final borderPaint = Paint()
+      ..color = Colors.black
+      ..style = PaintingStyle.stroke
+      ..strokeWidth = 1.0;
+    pictureCanvas.drawRect(bounds, borderPaint);
+
+    // 结束绘制
+    final recordedPicture = picture.endRecording();
+    final image =
+        await recordedPicture.toImage(size.width.toInt(), size.height.toInt());
+
+    // 转换为字节数组
+    final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
+    final bytes = byteData?.buffer.asUint8List();
+
+    return bytes;
   }
 
   // @override

+ 9 - 0
lib/pages/medical/widgets/ecg_view/view.dart

@@ -1,3 +1,4 @@
+import 'package:vitalapp/pages/medical/widgets/ecg_view/widgets/debug_dialog.dart';
 import 'package:flutter/material.dart';
 import 'package:get/get.dart';
 import 'index.dart';
@@ -35,6 +36,14 @@ class EcgView extends GetView<EcgViewController> {
       builder: (_) {
         return GestureDetector(
           onTap: () => controller.openFullScreenDialog(),
+          onDoubleTap: () async {
+            final imageBase64 = await controller.getFullDataImageBase64();
+            if (imageBase64.isNotEmpty) {
+              Get.dialog(
+                DebugDialog(imageBase64: imageBase64),
+              );
+            }
+          },
           child: Center(
             child: _buildView(),
           ),

+ 93 - 0
lib/pages/medical/widgets/ecg_view/widgets/debug_dialog.dart

@@ -0,0 +1,93 @@
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+
+import '../index.dart';
+
+class DebugDialog extends GetView<EcgViewController> {
+  const DebugDialog({Key? key, required this.imageBase64}) : super(key: key);
+
+  final String imageBase64;
+
+  // 主视图
+  Widget _buildView() {
+    const designWidth = 1280.0; // 设计尺寸宽度:1280
+    final width = Get.width;
+    final scale = width / designWidth; // 计算缩放比例
+    final ScrollController scrollController = ScrollController();
+    return Container(
+      width: Get.width * 0.9 / scale,
+      height: 240 * 3,
+      decoration: BoxDecoration(
+        color: Colors.white,
+        borderRadius: BorderRadius.circular(10),
+      ),
+      clipBehavior: Clip.antiAlias,
+      child: Column(
+        children: [
+          _buildHead(),
+          Expanded(
+            child: Scrollbar(
+              thumbVisibility: true,
+              thickness: 10,
+              radius: const Radius.circular(10),
+              controller: scrollController,
+              child: SingleChildScrollView(
+                controller: scrollController,
+                padding: const EdgeInsets.only(left: 10, right: 10, bottom: 20),
+                physics: const BouncingScrollPhysics(),
+                scrollDirection: Axis.horizontal,
+                child: Container(
+                  child: Center(
+                    child: displayImage(imageBase64),
+                  ),
+                ),
+              ),
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget displayImage(String base64Url) {
+    // 去掉"data:image/png;base64,"前缀
+    String base64Image = base64Url.split(',').last;
+
+    // 解码Base64字符串为字节数组
+    Uint8List imageData = base64Decode(base64Image);
+
+    return Image.memory(
+      imageData,
+      fit: BoxFit.cover, // 根据需要设置适当的fit属性
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Dialog(
+      child: _buildView(),
+    );
+  }
+
+  /// 构建弹窗顶部,右侧显示关闭按钮
+  Widget _buildHead() {
+    return SizedBox(
+      height: 50,
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.end,
+        children: [
+          IconButton(
+            onPressed: () => Get.back(),
+            icon: const Icon(
+              Icons.close,
+              size: 30,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+}