controller.dart 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. import 'dart:convert';
  2. import 'dart:math';
  3. import 'package:fis_jsonrpc/rpc.dart';
  4. import 'package:fis_measure/process/language/measure_language.dart';
  5. import 'package:fis_measure/process/workspace/rpc_helper.dart';
  6. import 'package:fis_measure/view/ai_result_modifier/state.dart';
  7. import 'package:fis_measure/view/mobile_view/widgets/throttle.dart' as utils;
  8. import 'package:flutter/gestures.dart';
  9. import 'package:flutter/material.dart';
  10. import 'package:get/get.dart';
  11. class AiResultModifierController extends GetxController {
  12. final rpcHelper = Get.find<RPCHelper>();
  13. /// 后台语言包控制器
  14. // final languageService = Get.find<LanguageService>();
  15. final state = AiResultModifierState();
  16. /// 传入的图像参数
  17. // String patientCode = "";
  18. String remedicalCode = "";
  19. // String recordCode = "";
  20. /// 初次查询到的完整数据
  21. AIDiagnosisPerImageDTO resultDTO = AIDiagnosisPerImageDTO();
  22. /// 编辑后的完整数据【用于发给后端】
  23. AIDiagnosisPerImageDTO modifiedDataDTO = AIDiagnosisPerImageDTO();
  24. // 用于画布绘制的轮廓点集
  25. List<Offset> _canvasContoursPoints = [];
  26. // 用于画布绘制的关键点集【拖拽模式】
  27. List<Offset> _canvasKeyPoints = [];
  28. // 用于画布绘制的高亮关键点集【拖拽模式】
  29. final List<Offset> _canvasAffectedKeyPoints = [];
  30. // 用于画布绘制的病灶大小横纵比例线段【四个坐标下标】
  31. List<int> _canvasLesionSizePointsIndexes = [];
  32. // 用于画布绘制的轮廓关键点下标集合【画轮廓模式】
  33. final List<int> _canvasPenModeKeyPointIndexes = [];
  34. // 用于画布绘制的轮廓关键点下标集合【画轮廓模式】
  35. final List<Offset> _canvasNewContoursPoints = [];
  36. // 播放器组件的key
  37. final List<Offset> _aiPoints = [];
  38. // 病灶结论列表
  39. List<EnumItemDTO> _diagnosisEnumItems = [];
  40. // 当前横线像素长度
  41. final int _horizontalLengthInPixel = 0;
  42. // 当前横线像素长度
  43. final int _verticalLengthInPixel = 0;
  44. // 当前AI病灶下标
  45. int currentAiDetectedObjectIndex = 0;
  46. GlobalKey framePlayerKey = GlobalKey();
  47. // 画布组件的大小
  48. Size aiCanvasSize = Size.zero;
  49. // 图像的实际大小
  50. Size frameSize = Size.zero;
  51. // 图像的缩放比例
  52. double _scale = 1.0;
  53. // 当前的轮廓点集
  54. List<AIDiagnosisPoint2D> contours = [];
  55. // 当前的病灶大小
  56. AIDiagnosisLesionSize? lesionSize;
  57. // 当前的关键点集
  58. List<DiagnosisKeyPointDTO> keyPoints = [];
  59. // 当前受影响的高亮的关键点下标集合
  60. List<int> affectedKeyPointIndexes = [];
  61. // 当前操作模式
  62. AiResultModifierMode _mode = AiResultModifierMode.drag;
  63. // 当前是否正在绘制新轮廓
  64. bool _isDrawingNewContours = false;
  65. // 拖拽起点
  66. Offset _dragStartPoint = Offset.zero;
  67. // 拖拽开始时的轮廓点集【仅用于发请求】
  68. List<AIDiagnosisPoint2D> contoursOnDragStart = [];
  69. // 拖拽开始时的关键点集【仅用于发请求】
  70. List<DiagnosisKeyPointDTO> keyPointsOnDragStart = [];
  71. /// 测量语言包
  72. final measureLanguage = MeasureLanguage();
  73. AiResultModifierController();
  74. /// 多个ai病灶
  75. List<AIDetectedObject> get aiDetectedObjectList =>
  76. modifiedDataDTO.diagResultsForEachOrgan?.first.detectedObjects ?? [];
  77. List<Offset> get aiPoints => _aiPoints;
  78. List<Offset> get canvasAffectedKeyPoints => _canvasAffectedKeyPoints;
  79. List<Offset> get canvasContoursPoints => _canvasContoursPoints;
  80. List<Offset> get canvasKeyPoints => _canvasKeyPoints;
  81. List<int> get canvasLesionSizePointsIndexes => _canvasLesionSizePointsIndexes;
  82. List<Offset> get canvasNewContoursPoints => _canvasNewContoursPoints;
  83. List<int> get canvasPenModeKeyPointIndexes => _canvasPenModeKeyPointIndexes;
  84. AiResultModifierMode get currMode => _mode;
  85. List<EnumItemDTO> get diagnosisEnumItems => _diagnosisEnumItems;
  86. /// 当前器官
  87. DiagnosisOrganEnum get diagnosisOrgan =>
  88. modifiedDataDTO.diagResultsForEachOrgan?.first.organ ??
  89. DiagnosisOrganEnum.Null;
  90. int get horizontalLengthInPixel => _horizontalLengthInPixel;
  91. double get scale => _scale;
  92. int get verticalLengthInPixel => _verticalLengthInPixel;
  93. /// 切换操作模式
  94. void changeModifierMode(AiResultModifierMode newMode) {
  95. if (_mode == newMode) return;
  96. _mode = newMode;
  97. _canvasAffectedKeyPoints.clear();
  98. update(['ai_result_modifier']);
  99. }
  100. /// 切换ai病灶
  101. void changeAiDetectedObjectIndex(int index) {
  102. currentAiDetectedObjectIndex = index;
  103. update(['ai_result_modifier', 'ai_result_panel']);
  104. }
  105. /// 获取AI模块的翻译值
  106. String getValuesFromAiLanguage(String code) {
  107. final value = measureLanguage.t('ai', code);
  108. return value;
  109. }
  110. /// 加载AI结果并调用绘制
  111. Future<void> loadAIResult() async {
  112. try {
  113. final result =
  114. await rpcHelper.rpc.remedical.getRemedicalDiagnosisDataAsync(
  115. GetRemedicalDiagnosisDataRequest(
  116. token: rpcHelper.userToken,
  117. remedicalCode: remedicalCode,
  118. frameIndex: 0,
  119. ),
  120. );
  121. resultDTO = AIDiagnosisPerImageDTO.fromJson(jsonDecode(result));
  122. modifiedDataDTO = AIDiagnosisPerImageDTO.fromJson(jsonDecode(result));
  123. contours =
  124. resultDTO.diagResultsForEachOrgan![0].detectedObjects![0].contours ??
  125. [];
  126. List<AIDiagnosisDescription>? descriptions = resultDTO
  127. .diagResultsForEachOrgan![0].detectedObjects![0].descriptions;
  128. //遍历 descriptions 取出病灶大小
  129. for (AIDiagnosisDescription description in descriptions!) {
  130. if (description.type == DiagnosisDescriptionEnum.LesionSize) {
  131. lesionSize = AIDiagnosisLesionSize.fromJson(
  132. jsonDecode(description.value ?? ""));
  133. }
  134. }
  135. keyPoints = await _queryAllKeyPoints();
  136. _updateCurrContoursPoints();
  137. _updateCurrKeyPoints();
  138. await _getDiagnosisEnumItemsAsync();
  139. update(['ai_result_canvas', 'ai_result_panel']);
  140. } catch (e) {
  141. print(e);
  142. }
  143. }
  144. @override
  145. void onClose() {
  146. super.onClose();
  147. print("AiResultModifierController close");
  148. }
  149. /// 图像尺寸加载完成的回调
  150. void onFrameDataLoaded(Size _frameSize) {
  151. print("图像尺寸 $_frameSize");
  152. frameSize = _frameSize;
  153. final RenderBox box =
  154. framePlayerKey.currentContext!.findRenderObject() as RenderBox;
  155. final framePlayerSize = Size(box.size.width, box.size.height);
  156. print("容器尺寸 $framePlayerSize");
  157. _scale = min(framePlayerSize.width / frameSize.width,
  158. framePlayerSize.height / frameSize.height);
  159. aiCanvasSize = Size(frameSize.width * _scale, frameSize.height * _scale);
  160. print("缩放比 $_scale");
  161. /// 更新交互层尺寸
  162. update(["ai_result_modifier_interactive_layer"]);
  163. }
  164. @override
  165. void onInit() {
  166. super.onInit();
  167. print("AiResultModifierController init");
  168. // 获取传递的参数
  169. final Map<String, String> args = Get.arguments;
  170. remedicalCode = args["remedicalCode"] ?? "";
  171. print(args);
  172. }
  173. /// 鼠标拖拽
  174. void onMouseDrag(DragUpdateDetails details) {
  175. switch (_mode) {
  176. case AiResultModifierMode.drag:
  177. utils.throttle(() {
  178. _onDragModeCallDragFunction(details.localPosition);
  179. }, 'onMouseDrag', 100);
  180. break;
  181. case AiResultModifierMode.pen:
  182. _onPenModeCallDragFunction(details.localPosition);
  183. break;
  184. default:
  185. }
  186. }
  187. /// 鼠标拖拽结束
  188. void onMouseDragEnd(DragEndDetails details) async {
  189. switch (_mode) {
  190. case AiResultModifierMode.drag:
  191. break;
  192. case AiResultModifierMode.pen:
  193. if (_isDrawingNewContours) {
  194. _isDrawingNewContours = false;
  195. await _callContourMergeAsync();
  196. _updateCurrContoursPoints();
  197. _updateCurrKeyPoints();
  198. }
  199. _canvasNewContoursPoints.clear();
  200. update(['ai_result_canvas']);
  201. break;
  202. default:
  203. }
  204. }
  205. /// 鼠标拖拽开始【记录起点】
  206. void onMouseDragStart(DragDownDetails details) {
  207. switch (_mode) {
  208. case AiResultModifierMode.drag:
  209. _dragStartPoint = details.localPosition;
  210. contoursOnDragStart = contours;
  211. keyPointsOnDragStart = keyPoints;
  212. break;
  213. case AiResultModifierMode.pen:
  214. if (_canvasPenModeKeyPointIndexes.isNotEmpty) {
  215. _isDrawingNewContours = true;
  216. _dragStartPoint = details.localPosition;
  217. _canvasNewContoursPoints.clear();
  218. _canvasNewContoursPoints
  219. .add(_canvasContoursPoints[_canvasPenModeKeyPointIndexes[0]]);
  220. _canvasNewContoursPoints.add(_dragStartPoint);
  221. }
  222. break;
  223. default:
  224. }
  225. }
  226. /// 鼠标悬浮移动
  227. void onMouseHover(PointerHoverEvent e) async {
  228. if (keyPoints.isEmpty) return;
  229. switch (_mode) {
  230. case AiResultModifierMode.drag:
  231. utils.throttle(() {
  232. _onDragModeCallHoverFunction(e.localPosition);
  233. }, 'onMouseHover', 100);
  234. break;
  235. case AiResultModifierMode.pen:
  236. print("画笔模式,遍历查找最近的点");
  237. utils.throttle(() {
  238. _onPenModeCallHoverFunction(e.localPosition);
  239. }, 'onMouseHover', 10);
  240. // Offset point = e.localPosition;
  241. break;
  242. default:
  243. }
  244. }
  245. @override
  246. void onReady() {
  247. super.onReady();
  248. _initData();
  249. }
  250. /// 保存AI修改结果
  251. Future<void> saveAIResult({
  252. String? code,
  253. int frameIndex = 0,
  254. }) async {
  255. try {
  256. final result =
  257. await rpcHelper.rpc.remedical.saveRemedicalAISelectedInfoAsync(
  258. SaveRemedicalAISelectedInfoRequest(
  259. token: rpcHelper.userToken,
  260. remedicalCode: remedicalCode,
  261. code: code,
  262. frameIndex: frameIndex,
  263. diagnosisData: jsonEncode(resultDTO),
  264. ),
  265. );
  266. } catch (e) {
  267. print(e);
  268. }
  269. }
  270. /// 自动吸附闭合判断
  271. void _autoCloseContours() async {
  272. if (_canvasNewContoursPoints.length < 6) return;
  273. double minDistance = double.infinity;
  274. int nearestKeyPointIndex = -1;
  275. final lastPoint = _canvasNewContoursPoints.last;
  276. /// 遍历所有关键点keyPoints,找到离localPosition最近的关键点
  277. for (int i = 0; i < canvasContoursPoints.length; i++) {
  278. final point = canvasContoursPoints[i];
  279. final double distance = (point - lastPoint).distance;
  280. if (distance < minDistance) {
  281. minDistance = distance;
  282. nearestKeyPointIndex = i;
  283. }
  284. }
  285. print("最小距离 $minDistance");
  286. if (minDistance < 6) {
  287. print("吸附成功");
  288. _canvasPenModeKeyPointIndexes.add(nearestKeyPointIndex);
  289. _canvasNewContoursPoints.add(canvasContoursPoints[nearestKeyPointIndex]);
  290. _isDrawingNewContours = false;
  291. await _callContourMergeAsync();
  292. _updateCurrContoursPoints();
  293. _updateCurrKeyPoints();
  294. }
  295. }
  296. /// 发送请求通知后端合并轮廓
  297. Future<bool> _callContourMergeAsync() async {
  298. final ContourMergeResult result =
  299. await rpcHelper.rpc.aIDiagnosis.contourMergeAsync(
  300. ContourMergeRequest(
  301. token: rpcHelper.userToken,
  302. contourPoints: contours,
  303. lesionSize: lesionSize,
  304. drawingNewContourPoints: _convertCanvasPoints(_canvasNewContoursPoints),
  305. ),
  306. );
  307. //TODO:此处可以拿到合并后的纵横比数据 to Baka
  308. print(result);
  309. contours = result.dstContours ?? [];
  310. lesionSize = result.dstLesionSize;
  311. keyPoints = await _queryAllKeyPoints();
  312. return true;
  313. // if (result.success) {
  314. // // _initData();
  315. // }
  316. }
  317. /// 画布坐标系转换【画布坐标系 -> 接口坐标系】
  318. List<AIDiagnosisPoint2D> _convertCanvasPoints(List<Offset> points) {
  319. List<AIDiagnosisPoint2D> result = [];
  320. for (Offset point in points) {
  321. result.add(
  322. AIDiagnosisPoint2D(x: point.dx ~/ _scale, y: point.dy ~/ _scale));
  323. }
  324. return result;
  325. }
  326. /// 关键点坐标转换【接口坐标系 -> 画布坐标系】同时更新横纵比例线段下标
  327. List<Offset> _convertKeyPoints(List<DiagnosisKeyPointDTO> points) {
  328. List<Offset> result = [];
  329. List<int> pointIndexes = List.generate(4, (_) => 0);
  330. for (int i = 0; i < points.length; i++) {
  331. final point = points[i];
  332. if (point.point == null) continue;
  333. result.add(Offset(point.point!.x.toDouble() * _scale,
  334. point.point!.y.toDouble() * _scale));
  335. if (point.type != DiagnosisKeyPointType.OtherKeyPoints) {
  336. switch (point.type) {
  337. case DiagnosisKeyPointType.HorizontalPointLeft:
  338. pointIndexes[0] = i;
  339. break;
  340. case DiagnosisKeyPointType.HorizontalPointRight:
  341. pointIndexes[1] = i;
  342. break;
  343. case DiagnosisKeyPointType.VerticalPointUp:
  344. pointIndexes[2] = i;
  345. break;
  346. case DiagnosisKeyPointType.VerticalPointDown:
  347. pointIndexes[3] = i;
  348. break;
  349. default:
  350. }
  351. }
  352. }
  353. _canvasLesionSizePointsIndexes = pointIndexes;
  354. return result;
  355. }
  356. /// 坐标转换【接口坐标系 -> 画布坐标系】
  357. List<Offset> _convertPoints(List<AIDiagnosisPoint2D> points) {
  358. List<Offset> result = [];
  359. for (AIDiagnosisPoint2D point in points) {
  360. result.add(
  361. Offset(point.x.toDouble() * _scale, point.y.toDouble() * _scale));
  362. }
  363. return result;
  364. }
  365. /// 获取ai结果相关枚举集合
  366. Future<void> _getDiagnosisEnumItemsAsync() async {
  367. final getDiagnosisEnumItems =
  368. await rpcHelper.rpc.aIDiagnosis.getDiagnosisEnumItemsAsync(
  369. GetDiagnosisEnumItemsRequest(
  370. token: rpcHelper.userToken,
  371. ),
  372. );
  373. _diagnosisEnumItems = getDiagnosisEnumItems.source ?? [];
  374. }
  375. void _initData() {
  376. update(["ai_result_modifier"]);
  377. }
  378. /// 在拖拽模式下触发拖拽事件【每隔100ms触发一次】
  379. void _onDragModeCallDragFunction(Offset pos) async {
  380. print("鼠标拖拽 $_dragStartPoint -> $pos");
  381. AIDiagnosisPoint2D startPoint = AIDiagnosisPoint2D(
  382. x: _dragStartPoint.dx ~/ _scale, y: _dragStartPoint.dy ~/ _scale);
  383. AIDiagnosisPoint2D endPoint =
  384. AIDiagnosisPoint2D(x: pos.dx ~/ _scale, y: pos.dy ~/ _scale);
  385. final bool success = await _queryDragResult(startPoint, endPoint);
  386. if (success) {
  387. _updateCurrKeyPoints();
  388. _updateCurrContoursPoints();
  389. _updateCurrAffectedKeyPoints();
  390. update(["ai_result_canvas"]);
  391. }
  392. }
  393. /// 在拖拽模式下,通过鼠标位置更新高亮的关键点下标【每隔100ms触发一次】
  394. void _onDragModeCallHoverFunction(Offset localPosition) async {
  395. final mousePos = AIDiagnosisPoint2D(
  396. x: localPosition.dx ~/ _scale, y: localPosition.dy ~/ _scale);
  397. affectedKeyPointIndexes = await _queryAffectedKeyPoints(mousePos);
  398. print("影响到的关键点数量:${affectedKeyPointIndexes.length}");
  399. _updateCurrAffectedKeyPoints();
  400. update(["ai_result_canvas"]);
  401. }
  402. /// 在画轮廓模式下触发拖拽事件
  403. void _onPenModeCallDragFunction(Offset pos) async {
  404. if (!_isDrawingNewContours) return;
  405. // 点间距【疏密程度】
  406. const double pointDistance = 8;
  407. final double distance = (pos - _canvasNewContoursPoints.last).distance;
  408. print("当前点到上一个点的距离:$distance");
  409. if (distance >= pointDistance) {
  410. int numPointsToInsert = (distance / pointDistance).ceil() - 1; // 需要插入的点数
  411. for (int i = 0; i < numPointsToInsert; i++) {
  412. double t = (i + 1) / (numPointsToInsert + 1);
  413. Offset interpolatedPoint = Offset(
  414. _canvasNewContoursPoints.last.dx +
  415. t * (pos.dx - _canvasNewContoursPoints.last.dx),
  416. _canvasNewContoursPoints.last.dy +
  417. t * (pos.dy - _canvasNewContoursPoints.last.dy),
  418. );
  419. _canvasNewContoursPoints.add(interpolatedPoint);
  420. }
  421. _canvasNewContoursPoints.add(pos);
  422. update(["ai_result_canvas"]);
  423. }
  424. print("当前轮廓点数量:${_canvasNewContoursPoints.length}");
  425. _autoCloseContours();
  426. }
  427. /// 在画轮廓模式下,通过鼠标位置更新最近的关键点【每隔10ms触发一次】
  428. void _onPenModeCallHoverFunction(Offset localPosition) async {
  429. double minDistance = double.infinity;
  430. // Offset nearestKeyPoint = Offset.zero;
  431. int nearestKeyPointIndex = -1;
  432. /// 遍历所有关键点keyPoints,找到离localPosition最近的关键点
  433. for (int i = 0; i < canvasContoursPoints.length; i++) {
  434. final point = canvasContoursPoints[i];
  435. final double distance = (point - localPosition).distance;
  436. if (distance < minDistance) {
  437. minDistance = distance;
  438. // nearestKeyPoint = point;
  439. nearestKeyPointIndex = i;
  440. }
  441. }
  442. _canvasPenModeKeyPointIndexes.clear();
  443. if (minDistance < 10) {
  444. _canvasPenModeKeyPointIndexes.add(nearestKeyPointIndex);
  445. }
  446. update(["ai_result_canvas"]);
  447. }
  448. /// 根据鼠标位置查询受影响的关键点
  449. Future<List<int>> _queryAffectedKeyPoints(AIDiagnosisPoint2D mousePos) async {
  450. try {
  451. final List<int> result =
  452. await rpcHelper.rpc.aIDiagnosis.affectedKeyPointsByDragActionAsync(
  453. AffectedKeyPointsByDragActionRequest(
  454. token: rpcHelper.userToken,
  455. keyPoints: keyPoints,
  456. mousePoint: mousePos,
  457. ),
  458. );
  459. // print(result);
  460. return result;
  461. } catch (e) {
  462. print(e);
  463. return [];
  464. }
  465. }
  466. /// 查询所有关键点【需要先存好contours和lesionSize】
  467. Future<List<DiagnosisKeyPointDTO>> _queryAllKeyPoints() async {
  468. try {
  469. final List<DiagnosisKeyPointDTO> result =
  470. await rpcHelper.rpc.aIDiagnosis.getKeyPointsOfContourAsync(
  471. GetKeyPointsOfContourRequest(
  472. token: rpcHelper.userToken,
  473. contours: contours,
  474. lesionSize: lesionSize,
  475. ),
  476. );
  477. return result;
  478. } catch (e) {
  479. print(e);
  480. return [];
  481. }
  482. }
  483. /// 查询拖拽结果集合【需要先存好 contoursOnDragStart 和 keyPointsOnDragStart】
  484. Future<bool> _queryDragResult(
  485. AIDiagnosisPoint2D startPoint, AIDiagnosisPoint2D endPoint) async {
  486. try {
  487. final ContourAndKeyPointsAfterDragResult result =
  488. await rpcHelper.rpc.aIDiagnosis.contourAndKeyPointsAfterDragAsync(
  489. ContourAndKeyPointsAfterDragRequest(
  490. token: rpcHelper.userToken,
  491. contours: contoursOnDragStart,
  492. keyPoints: keyPointsOnDragStart,
  493. startPoint: startPoint,
  494. endPoint: endPoint,
  495. ),
  496. );
  497. keyPoints = result.dstKeyPoints ?? [];
  498. contours = result.dstContours ?? [];
  499. affectedKeyPointIndexes = result.affectedKeyPointIndexes!;
  500. print("拖拽结果:${keyPoints.length} ${contours.length}");
  501. return true;
  502. } catch (e) {
  503. print(e);
  504. return false;
  505. }
  506. }
  507. /// [⭐ _canvasAffectedKeyPoints ] 根据当前的受影响关键点下标更新受影响关键点集
  508. void _updateCurrAffectedKeyPoints() {
  509. _canvasAffectedKeyPoints.clear();
  510. if (keyPoints.isEmpty) return;
  511. for (int i = 0; i < keyPoints.length; i++) {
  512. if (affectedKeyPointIndexes.contains(i)) {
  513. _canvasAffectedKeyPoints.add(Offset(
  514. keyPoints[i].point!.x.toDouble() * _scale,
  515. keyPoints[i].point!.y.toDouble() * _scale));
  516. }
  517. }
  518. print("受影响的点数:${_canvasAffectedKeyPoints.length}");
  519. }
  520. /// [⭐ _canvasContoursPoints ] 更新当前轮廓点
  521. void _updateCurrContoursPoints() {
  522. _canvasContoursPoints = _convertPoints(contours);
  523. print("轮廓点数:${_canvasContoursPoints.length}");
  524. }
  525. /// [⭐ _canvasKeyPoints ] 更新当前关键点
  526. void _updateCurrKeyPoints() async {
  527. _canvasKeyPoints = _convertKeyPoints(keyPoints);
  528. print("关键点数:${_canvasKeyPoints.length}");
  529. }
  530. }
  531. enum AiResultModifierMode {
  532. /// 拖拽
  533. drag,
  534. /// 画笔
  535. pen,
  536. }