controller.dart 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'dart:ui';
  4. import 'package:flutter/foundation.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:get/get.dart';
  7. import 'package:vitalapp/pages/medical/controller.dart';
  8. import 'package:vitalapp/pages/medical/widgets/twelve_ecg_view/state.dart';
  9. import 'package:vitalapp/pages/medical/widgets/twelve_ecg_view/widgets/grid_background_printer_for_five.dart';
  10. import 'dart:ui' as ui;
  11. import 'index.dart';
  12. class TwelveEcgViewController extends GetxController {
  13. TwelveEcgViewController({
  14. required this.initPoints,
  15. });
  16. final state = TwelveEcgState();
  17. final MedicalController medicalController = Get.find<MedicalController>();
  18. List<int> initPoints;
  19. /// 每秒的数据量
  20. static int get dataPerSecond => 200;
  21. /// 更新帧率
  22. static int updateFrameRate = 15;
  23. /// 更新周期
  24. static int updatePeriod = 1000 ~/ updateFrameRate;
  25. /// 画布时间跨度(秒)
  26. int get timeSpan => 5;
  27. /// 横坐标数据量
  28. int get xDataCount => (dataPerSecond * timeSpan).toInt();
  29. /// 所有心电数据
  30. List<int> allPoints = [];
  31. /// 需绘制的新数据
  32. List<List<int>> newPointsToDraw = [
  33. [],
  34. [],
  35. [],
  36. [],
  37. [],
  38. [],
  39. [],
  40. [],
  41. [],
  42. [],
  43. [],
  44. []
  45. ];
  46. /// 需绘制的历史数据
  47. List<List<int>> oldPointsToDraw = [
  48. [],
  49. [],
  50. [],
  51. [],
  52. [],
  53. [],
  54. [],
  55. [],
  56. [],
  57. [],
  58. [],
  59. []
  60. ];
  61. List<double> yMaxList = [
  62. 1000,
  63. 5000,
  64. 2000,
  65. 8000,
  66. 8000,
  67. 8000,
  68. 1000,
  69. 8000,
  70. 8000,
  71. 8000,
  72. 8000,
  73. 8000,
  74. ];
  75. /// 启动时时间戳
  76. int startTime = DateTime.now().millisecondsSinceEpoch;
  77. /// 当前数据位(根据时间戳计算)
  78. int currentDataIndex = 0;
  79. /// 数据刷新定时器
  80. Timer timer = Timer(Duration.zero, () {});
  81. /// 发生错误时暂停了
  82. bool isPaused = false;
  83. /// 是否有初始值
  84. bool isInitPoints = false;
  85. /// 读取到数据的回调
  86. void addData(List<int> data) {
  87. if (allPoints.isEmpty) {
  88. startTime = DateTime.now().millisecondsSinceEpoch;
  89. _startTimer();
  90. }
  91. allPoints.addAll(data);
  92. if (isPaused) {
  93. isPaused = false;
  94. _startTimer();
  95. }
  96. }
  97. /// 打开全屏心电图弹窗
  98. void openFullScreenDialog() {
  99. var startDate =
  100. medicalController.diagnosisDataValue['TwelveHeart']?['StartDate'] ?? "";
  101. var endDate =
  102. medicalController.diagnosisDataValue['TwelveHeart']?['EndDate'] ?? '';
  103. if (startDate.toString() != "" && endDate.toString() != "")
  104. state.rangeValues = RangeValues(
  105. double.parse(startDate.toString()), double.parse(endDate.toString()));
  106. Get.dialog(
  107. const FullScreenEcgDataDialog(),
  108. );
  109. }
  110. // 重置
  111. void reset() {
  112. timer.cancel();
  113. isPaused = false;
  114. allPoints = [];
  115. newPointsToDraw = [[], [], [], [], [], [], [], [], [], [], [], []];
  116. oldPointsToDraw = [[], [], [], [], [], [], [], [], [], [], [], []];
  117. startTime = DateTime.now().millisecondsSinceEpoch;
  118. currentDataIndex = 0;
  119. update(['twelve_ecg_view']);
  120. }
  121. /// 开始采集
  122. void startSave() {
  123. timer.cancel();
  124. allPoints = [];
  125. startTime = DateTime.now().millisecondsSinceEpoch;
  126. currentDataIndex = 0;
  127. }
  128. /// 因为实时数据请求量太大,就是延迟俩秒数据
  129. void updateTwelveEcgView() {
  130. initPoints = allPoints;
  131. update(['twelve_ecg_view']);
  132. }
  133. /// 获取完整心电图的base64(带base64头)
  134. Future<String> getFullDataImageBase64() async {
  135. final painter = EcgPainterForAll(
  136. allPoints: allPoints,
  137. yMaxList: yMaxList,
  138. );
  139. final bgPainter =
  140. GridBackgroundPainterForAll(rangeValues: RangeValues(0, 0));
  141. const size = Size(5000, 650);
  142. // 使用离屏Canvas绘制
  143. final Uint8List? bytes =
  144. await _capturePainterToImage(painter, bgPainter, size);
  145. if (bytes == null) {
  146. return "";
  147. } else {
  148. return _convertToBase64Url(bytes);
  149. }
  150. }
  151. List<List<int>> splitArrayIntoChunks(List<int> array, int chunkSize) {
  152. List<List<int>> chunks = [];
  153. for (int i = 0; i < array.length; i += chunkSize) {
  154. chunks.add(array.sublist(
  155. i, i + chunkSize > array.length ? array.length : i + chunkSize));
  156. }
  157. return chunks;
  158. }
  159. List<List<int>> splitArrayIntoChunks2() {
  160. List<List<int>> chunks = [];
  161. var satrtPoint = (allPoints.length * (state.rangeValues.start / 30)).ceil();
  162. var dataLength = (allPoints.length *
  163. ((state.rangeValues.end - state.rangeValues.start) / 30))
  164. .ceil();
  165. chunks.add(allPoints.sublist(satrtPoint, satrtPoint + dataLength));
  166. return chunks;
  167. }
  168. updateEcgImage() async {
  169. medicalController.diagnosisDataValue['TwelveHeart']?['ECG12'] =
  170. await getFiveImageBase64();
  171. medicalController.diagnosisDataValue['TwelveHeart']?['StartDate'] =
  172. state.rangeValues.start.toString();
  173. medicalController.diagnosisDataValue['TwelveHeart']?['EndDate'] =
  174. state.rangeValues.end.toString();
  175. }
  176. /// 获取五秒图心电图的base64(带base64头)
  177. Future<String> getFiveImageBase64() async {
  178. List<List<int>> chunks = splitArrayIntoChunks2();
  179. List<int> lastChunk = chunks.last;
  180. final painter = EcgPainterForAll(
  181. allPoints: lastChunk,
  182. yMaxList: yMaxList,
  183. );
  184. final bgPainter = GridBackgroundPainterForFive();
  185. // double width =
  186. // ((state.rangeValues.end - state.rangeValues.start) / 30) * 3200;
  187. Size size = Size(833, 650);
  188. // 使用离屏Canvas绘制
  189. final Uint8List? bytes =
  190. await _capturePainterToImage(painter, bgPainter, size);
  191. if (bytes == null) {
  192. return "";
  193. } else {
  194. return _convertToBase64Url(bytes);
  195. }
  196. }
  197. /// 开启定时器,每隔一定时间添加一次数据,并且更新UI
  198. void _startTimer() {
  199. timer = Timer.periodic(
  200. Duration(milliseconds: updatePeriod),
  201. (timer) {
  202. // print("timer: ${timer.tick}");
  203. _updateData();
  204. },
  205. );
  206. }
  207. List<List<int>> separateLines(List<int> array) {
  208. // 将数据拆分成12条线的数据
  209. List<List<int>> lines = List.generate(12, (_) => []);
  210. for (int i = 0; i < array.length; i++) {
  211. int lineIndex = i % 12; // 计算当前数据所属的线的索引
  212. lines[lineIndex].add(array[i]); // 将数据添加到对应的线中
  213. }
  214. return lines;
  215. }
  216. /// 每帧更新数据
  217. void _updateData() {
  218. // 计算当前数据位
  219. currentDataIndex = (DateTime.now().millisecondsSinceEpoch - startTime) ~/
  220. (1000 ~/ dataPerSecond);
  221. /// 需显示的数据量 不能为0
  222. int needDataCount = (currentDataIndex % xDataCount) == 0
  223. ? 25
  224. : currentDataIndex % xDataCount;
  225. /// 当前周期数
  226. int currentPeriod = currentDataIndex ~/ xDataCount;
  227. // 计算新数据
  228. try {
  229. for (int i = 0; i < 12; i++) {
  230. newPointsToDraw[i] = separateLines(allPoints)[i].sublist(
  231. currentDataIndex - needDataCount,
  232. currentDataIndex,
  233. );
  234. if (currentPeriod > 0) {
  235. oldPointsToDraw[i] = separateLines(allPoints)[i].sublist(
  236. (currentPeriod - 1) * xDataCount,
  237. currentPeriod * xDataCount,
  238. );
  239. }
  240. }
  241. if (currentDataIndex > dataPerSecond * 30) {
  242. startSave();
  243. }
  244. print(
  245. "update newPointsToDraw: $currentDataIndex $needDataCount ${newPointsToDraw[0].length} --currentPeriod $currentPeriod");
  246. } catch (e) {
  247. print("allPoints.length${allPoints.length}");
  248. timer.cancel();
  249. isPaused = true;
  250. }
  251. update(['twelve_ecg_view']);
  252. }
  253. /// 将字节数组转换为base64
  254. String _convertToBase64Url(Uint8List imageData) {
  255. String base64Image = base64Encode(imageData);
  256. String base64Url = base64Image;
  257. return base64Url;
  258. }
  259. /// 将CustomPainter绘制的内容转换为图片
  260. Future<Uint8List?> _capturePainterToImage(
  261. CustomPainter painter, CustomPainter bgPainter, Size size) async {
  262. final bounds = Offset.zero & size;
  263. final picture = PictureRecorder();
  264. final pictureCanvas = Canvas(picture);
  265. // 给Canvas设置绘制范围
  266. pictureCanvas.clipRect(bounds);
  267. /// 绘制背景(纯白)
  268. pictureCanvas.drawColor(Colors.white, BlendMode.color);
  269. // 在Canvas上进行绘制
  270. bgPainter.paint(pictureCanvas, size);
  271. painter.paint(pictureCanvas, size);
  272. /// 绘制一圈边框
  273. final borderPaint = Paint()
  274. ..color = Colors.black
  275. ..style = PaintingStyle.stroke
  276. ..strokeWidth = 1.0;
  277. pictureCanvas.drawRect(bounds, borderPaint);
  278. // 结束绘制
  279. final recordedPicture = picture.endRecording();
  280. final image =
  281. await recordedPicture.toImage(size.width.toInt(), size.height.toInt());
  282. // 转换为字节数组
  283. final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
  284. final bytes = byteData?.buffer.asUint8List();
  285. return bytes;
  286. }
  287. // @override
  288. // void onInit() {
  289. // super.onInit();
  290. // }
  291. @override
  292. void onReady() {
  293. super.onReady();
  294. if (initPoints.isNotEmpty) {
  295. isInitPoints = true;
  296. List<List<int>> initPointsLists = separateLines(initPoints);
  297. for (int i = 0; i < 12; i++) {
  298. newPointsToDraw[i] = initPointsLists[i];
  299. }
  300. update(['twelve_ecg_view']);
  301. allPoints = initPoints;
  302. }
  303. // if (initPoints.length >= 3750) {
  304. // newPointsToDraw = initPoints.sublist(3375, 3750);
  305. // update(['twelve_ecg_view']);
  306. // allPoints = initPoints;
  307. // } else {
  308. // newPointsToDraw = initPoints;
  309. // update(['twelve_ecg_view']);
  310. // allPoints = initPoints;
  311. // }
  312. }
  313. @override
  314. void onClose() {
  315. super.onClose();
  316. timer.cancel();
  317. }
  318. }