ellipse.dart 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. import 'dart:ui';
  2. import 'dart:math' as math;
  3. import 'package:fis_measure/interfaces/date_types/point.dart';
  4. import 'package:fis_measure/interfaces/enums/items.dart';
  5. import 'package:fis_measure/interfaces/process/items/item.dart';
  6. import 'package:fis_measure/interfaces/process/items/item_metas.dart';
  7. import 'package:fis_measure/interfaces/process/workspace/point_info.dart';
  8. import 'package:fis_measure/process/calcuators/calculator.dart';
  9. import 'package:fis_measure/process/calcuators/curve.dart';
  10. import 'package:fis_measure/process/calcuators/formulas/general.dart';
  11. import 'package:fis_measure/process/workspace/urm/application.dart';
  12. import 'package:fis_measure/utils/canvas.dart';
  13. import 'package:path_drawing/path_drawing.dart';
  14. import 'package:vid/us/vid_us_unit.dart';
  15. import 'area_abstract.dart';
  16. class Ellipse extends AreaItemAbstract {
  17. Ellipse(ItemMeta meta, IMeasureItem? parent) : super(meta, parent);
  18. @override
  19. bool onExecuteMouse(PointInfo args) {
  20. if (state == ItemStates.finished) {
  21. if (args.pointType == PointInfoType.mouseDown) {
  22. state = ItemStates.waiting;
  23. }
  24. }
  25. if (state == ItemStates.waiting) {
  26. if (args.pointType == PointInfoType.mouseDown) {
  27. handleMouseDownWhileWaiting(args);
  28. }
  29. } else if (state == ItemStates.running) {
  30. if (feature == null) return false;
  31. final f = feature! as EllipseFeature;
  32. final activeIndex = f.activeIndex;
  33. switch (args.pointType) {
  34. case PointInfoType.mouseMove:
  35. f.innerPoints[activeIndex] = args;
  36. f.adjustPoints(args);
  37. break;
  38. case PointInfoType.mouseDown:
  39. if (activeIndex == 1) {
  40. if (f.xAxisEnd == f.xAxisStart) {
  41. break;
  42. }
  43. f.adjustPoints(args);
  44. f.activeIndex = 2;
  45. } else if (activeIndex == 2) {
  46. doFeatureFinish();
  47. }
  48. break;
  49. default:
  50. return false;
  51. }
  52. doCalculate();
  53. return true;
  54. }
  55. return false;
  56. }
  57. bool isFirstPointMove = true;
  58. DPoint touchStartPosition = DPoint(0, 0); // 相对位移起始触摸点
  59. DPoint currPointLastPosition = DPoint(0, 0); // 当前操作的点之前所在位置
  60. @override
  61. bool onExecuteTouch(PointInfo args) {
  62. if (state == ItemStates.finished) {
  63. if (args.pointType == PointInfoType.touchDown) {
  64. state = ItemStates.waiting;
  65. }
  66. }
  67. if (state == ItemStates.waiting) {
  68. if (args.pointType == PointInfoType.touchDown) {
  69. isFirstPointMove = true;
  70. handleTouchDownWhileWaiting(args);
  71. }
  72. } else if (state == ItemStates.running) {
  73. if (feature == null) return false;
  74. final f = feature! as EllipseFeature;
  75. if (args.pointType == PointInfoType.touchDown) {
  76. touchStartPosition = args;
  77. currPointLastPosition = f.innerPoints[f.activeIndex];
  78. }
  79. if (args.pointType == PointInfoType.touchMove) {
  80. if (isFirstPointMove) {
  81. PointInfo newStartPoint = args;
  82. newStartPoint.addOffset(0, -0.2);
  83. if (isMoveTargetOutOfRange(newStartPoint)) return true;
  84. for (var element in f.innerPoints) {
  85. element.update(newStartPoint);
  86. }
  87. } else {
  88. PointInfo newPoint = PointInfo.fromOffset(
  89. currPointLastPosition
  90. .clone()
  91. .addVector(args - touchStartPosition)
  92. .toOffset(),
  93. args.pointType);
  94. newPoint.hostVisualArea = args.hostVisualArea;
  95. if (isMoveTargetOutOfRange(newPoint)) return true;
  96. f.innerPoints[f.activeIndex] = newPoint;
  97. f.adjustPoints(newPoint);
  98. }
  99. }
  100. if (args.pointType == PointInfoType.touchUp) {
  101. if (isFirstPointMove) {
  102. isFirstPointMove = false;
  103. f.activeIndex = 1;
  104. }
  105. if (f.activeIndex == 1) {
  106. if (f.xAxisEnd != f.xAxisStart) {
  107. f.adjustPoints(args);
  108. f.activeIndex = 2;
  109. }
  110. } else if (f.activeIndex == 2) {
  111. doFeatureFinish();
  112. }
  113. }
  114. doCalculate();
  115. return true;
  116. }
  117. return false;
  118. }
  119. void handleMouseDownWhileWaiting(PointInfo args) {
  120. // TODO: 判断是否当前area
  121. // 转换为Area逻辑位置
  122. final point = args.toAreaLogicPoint();
  123. feature = EllipseFeature(this, point);
  124. if (args.hostVisualArea != null) {
  125. feature!.hostVisualArea = args.hostVisualArea;
  126. }
  127. feature!.activeIndex = 1;
  128. state = ItemStates.running;
  129. }
  130. void handleTouchDownWhileWaiting(PointInfo args) {
  131. // TODO: 判断是否当前area
  132. // 转换为Area逻辑位置
  133. final point = args.toAreaLogicPoint();
  134. feature = EllipseFeature(this, point);
  135. if (args.hostVisualArea != null) {
  136. feature!.hostVisualArea = args.hostVisualArea;
  137. }
  138. feature!.activeIndex = 0;
  139. state = ItemStates.running;
  140. }
  141. static Ellipse createAreaPerimeter(ItemMeta meta, [IMeasureItem? parent]) {
  142. final ellipse = Ellipse(meta, parent);
  143. ellipse.calculator = AreaPerimeterEllipseCal(ellipse);
  144. return ellipse;
  145. }
  146. static Ellipse createVolume(ItemMeta meta, [IMeasureItem? parent]) {
  147. final ellipse = Ellipse(meta, parent);
  148. ellipse.calculator = _EllipseVolumeCal(ellipse);
  149. return ellipse;
  150. }
  151. }
  152. class EllipseFeature extends AreaItemFeatureAbstract {
  153. EllipseFeature(Ellipse refItem, DPoint point) : super(refItem) {
  154. innerPoints.add(point.clone());
  155. innerPoints.add(point.clone());
  156. innerPoints.add(point.clone());
  157. innerPoints.add(point.clone());
  158. }
  159. /// 质心
  160. DPoint get centroid {
  161. final x = (xAxisStart.x + xAxisEnd.x) * 0.5;
  162. final y = (xAxisStart.y + xAxisEnd.y) * 0.5;
  163. return DPoint(x, y);
  164. }
  165. DPoint get xAxisStart => innerPoints[0];
  166. DPoint get xAxisEnd => innerPoints[1];
  167. DPoint get yAxisStart => innerPoints[2];
  168. DPoint get yAxisEnd => innerPoints[3];
  169. @override
  170. void paint(Canvas canvas, Size size) {
  171. if (innerPoints.isEmpty) return;
  172. drawId(canvas, size, idText);
  173. final xStartOffset = convert2ViewPoint(size, xAxisStart).toOffset();
  174. if (activeIndex < 1) {
  175. drawVertex(canvas, xStartOffset, true);
  176. return;
  177. } else {
  178. drawVertex(canvas, xStartOffset);
  179. }
  180. final xEndOffset = convert2ViewPoint(size, xAxisEnd).toOffset();
  181. canvas.drawDashLine(xStartOffset, xEndOffset, 1, 10, paintLinePan);
  182. final yStartOffset = convert2ViewPoint(size, innerPoints[2]).toOffset();
  183. final yEndOffset = convert2ViewPoint(size, innerPoints[3]).toOffset();
  184. final centroidOffset = convert2ViewPoint(size, centroid).toOffset();
  185. canvas.drawDashLine(yStartOffset, yEndOffset, 1, 10, paintLinePan);
  186. // canvas.drawDashLine(yStartOffset, xEndOffset, 1, 10, paintLinePan);
  187. // canvas.drawDashLine(yEndOffset, xEndOffset, 1, 10, paintLinePan);
  188. final radiusX = getRadiusX(size);
  189. final radiusY = getRadiusY(size);
  190. canvas.save();
  191. final path = Path();
  192. path.moveTo(xStartOffset.dx, xStartOffset.dy);
  193. // 以质心为原点旋转到水平方向
  194. canvas.translate(centroidOffset.dx, centroidOffset.dy);
  195. final rotateRad = (xEndOffset - xStartOffset).direction;
  196. canvas.rotate(rotateRad);
  197. canvas.translate(-centroidOffset.dx, -centroidOffset.dy);
  198. path.addOval(
  199. Rect.fromCenter(
  200. center: centroidOffset,
  201. width: radiusX * 2,
  202. height: radiusY * 2,
  203. ),
  204. );
  205. canvas.drawPath(
  206. dashPath(
  207. path,
  208. dashArray: CircularIntervalList<double>(<double>[2.0, 10.0]),
  209. ),
  210. paintPan,
  211. );
  212. canvas.restore();
  213. if (activeIndex == 1) {
  214. drawVertex(canvas, xEndOffset, true);
  215. drawVertex(canvas, yStartOffset);
  216. drawVertex(canvas, yEndOffset);
  217. } else if (activeIndex == 2) {
  218. drawVertex(canvas, xEndOffset);
  219. drawVertex(canvas, yEndOffset);
  220. drawVertex(canvas, yStartOffset, isActive);
  221. }
  222. }
  223. /// 绘制灌注图
  224. @override
  225. void paintPerfusion(Canvas canvas, Size size) {}
  226. void paintSharpOnly(Canvas canvas, Size size, {Paint? customPaintPen}) {
  227. if (innerPoints.isEmpty) return;
  228. final xStartOffset = convert2ViewPoint(size, xAxisStart).toOffset();
  229. final xEndOffset = convert2ViewPoint(size, xAxisEnd).toOffset();
  230. canvas.drawDashLine(
  231. xStartOffset, xEndOffset, 1, 10, customPaintPen ?? paintLinePan);
  232. final yStartOffset = convert2ViewPoint(size, innerPoints[2]).toOffset();
  233. final yEndOffset = convert2ViewPoint(size, innerPoints[3]).toOffset();
  234. final centroidOffset = convert2ViewPoint(size, centroid).toOffset();
  235. canvas.drawDashLine(
  236. yStartOffset, yEndOffset, 1, 10, customPaintPen ?? paintLinePan);
  237. final radiusX = getRadiusX(size);
  238. final radiusY = getRadiusY(size);
  239. canvas.save();
  240. final path = Path();
  241. path.moveTo(xStartOffset.dx, xStartOffset.dy);
  242. // 以质心为原点旋转到水平方向
  243. canvas.translate(centroidOffset.dx, centroidOffset.dy);
  244. final rotateRad = (xEndOffset - xStartOffset).direction;
  245. canvas.rotate(rotateRad);
  246. canvas.translate(-centroidOffset.dx, -centroidOffset.dy);
  247. path.addOval(
  248. Rect.fromCenter(
  249. center: centroidOffset,
  250. width: radiusX * 2,
  251. height: radiusY * 2,
  252. ),
  253. );
  254. canvas.drawPath(
  255. dashPath(
  256. path,
  257. dashArray: CircularIntervalList<double>(<double>[2.0, 10.0]),
  258. ),
  259. customPaintPen ?? paintPan,
  260. );
  261. canvas.restore();
  262. }
  263. /// X轴半径
  264. double getRadiusX([Size? fitSize]) {
  265. if (innerPoints.length < 2) {
  266. return 0;
  267. }
  268. var p1 = xAxisStart;
  269. var p2 = xAxisEnd;
  270. if (fitSize != null) {
  271. p1 = convert2ViewPoint(fitSize, p1);
  272. p2 = convert2ViewPoint(fitSize, p2);
  273. // p1 = p1.scale2Size(fitSize);
  274. // p2 = p2.scale2Size(fitSize);
  275. }
  276. return (p2 - p1).length / 2;
  277. }
  278. // 获取未缩放的基准半径
  279. double getBaseRadiusX([Size? fitSize]) {
  280. if (innerPoints.length < 2) {
  281. return 0;
  282. }
  283. var p1 = xAxisStart;
  284. var p2 = xAxisEnd;
  285. if (fitSize != null) {
  286. p1 = p1.scale2Size(fitSize);
  287. p2 = p2.scale2Size(fitSize);
  288. }
  289. return (p2 - p1).length / 2;
  290. }
  291. /// Y轴半径
  292. double getRadiusY([Size? fitSize]) {
  293. if (activeIndex > 1) {
  294. var p = yAxisStart;
  295. var p2 = yAxisEnd;
  296. var c = centroid;
  297. if (fitSize != null) {
  298. p = convert2ViewPoint(fitSize, p);
  299. p2 = convert2ViewPoint(fitSize, p2);
  300. c = convert2ViewPoint(fitSize, c);
  301. // p = p.scale2Size(fitSize);
  302. // p2 = p2.scale2Size(fitSize);
  303. // c = c.scale2Size(fitSize);
  304. }
  305. return (p - p2).length / 2;
  306. } else {
  307. return getRadiusX(fitSize);
  308. }
  309. }
  310. /// 圆周长
  311. double getCircumference([Size? fitSize]) {
  312. double a = getRadiusX(fitSize), b = getRadiusY(fitSize);
  313. if (a < b) {
  314. a = b;
  315. b = a;
  316. }
  317. return 2 * math.pi * b + 4 * (a - b);
  318. }
  319. /// 面积
  320. double getArea([Size? fitSize]) {
  321. return math.pi * getRadiusX(fitSize) * getRadiusY(fitSize);
  322. }
  323. /// 计算Y轴坐标点
  324. void adjustPoints(DPoint point) {
  325. if (activeIndex < 1) return;
  326. Size refSize = refItem.application.displaySize;
  327. // 判断是否为 URMApp 如果是左右双幅,则需要宽度减半,此处其实是需要一个当前画幅长宽比来确定垂直位置
  328. if (refItem.application is URMApplication &&
  329. (refItem.application as URMApplication).urmDataProcessor.isLeftRight) {
  330. refSize = Size(refSize.width / 2, refSize.height);
  331. }
  332. final restoreSize = Size(1 / refSize.width, 1 / refSize.height);
  333. final p = point.scale2Size(refSize);
  334. final p1 = xAxisStart.scale2Size(refSize);
  335. final p2 = xAxisEnd.scale2Size(refSize);
  336. final double radius;
  337. if (activeIndex == 2) {
  338. radius = GeneralFormulas.distance2Line(p1, p2, p);
  339. } else {
  340. radius = getBaseRadiusX(refSize);
  341. }
  342. final crossPoints = findCrossPoints(p2, p1, radius);
  343. final p3Index = findNearest(p, crossPoints);
  344. innerPoints[2] = crossPoints[p3Index].scale2Size(restoreSize);
  345. innerPoints[3] = crossPoints[1 - p3Index].scale2Size(restoreSize);
  346. }
  347. List<DPoint> findCrossPoints(DPoint a, DPoint b, distance) {
  348. final dx = b.x - a.x;
  349. final dy = b.y - a.y;
  350. final len = (b - a).length;
  351. /// 单位长度轴上距离增加量
  352. final udx = dx / len;
  353. final udy = dy / len;
  354. final px = -udy;
  355. final py = udx;
  356. final centroidX = (a.x + b.x) * 0.5;
  357. final centroidY = (a.y + b.y) * 0.5;
  358. final centroid = DPoint(centroidX, centroidY);
  359. final x1 = centroid.x + px * distance;
  360. final y1 = centroid.y + py * distance;
  361. final out1 = DPoint(x1, y1);
  362. final x2 = centroid.x - px * distance;
  363. final y2 = centroid.y - py * distance;
  364. final out2 = DPoint(x2, y2);
  365. return [out1, out2];
  366. }
  367. int findNearest(DPoint p, List<DPoint> source) {
  368. int rst = 0;
  369. double minLen = (source[0] - p).length;
  370. for (var i = 1; i < source.length; i++) {
  371. final len = (source[i] - p).length;
  372. if (len < minLen) {
  373. minLen = len;
  374. rst = i;
  375. }
  376. }
  377. return rst;
  378. }
  379. }
  380. class _EllipseVolumeCal extends Calculator<Ellipse, double> {
  381. _EllipseVolumeCal(Ellipse ref) : super(ref);
  382. @override
  383. void calculate() {
  384. if (ref.feature == null) return;
  385. final feature = ref.feature! as EllipseFeature;
  386. final viewport = feature.hostVisualArea!.viewport!;
  387. final size = viewport.convertBoundary;
  388. double xAxis = feature.getRadiusX(size);
  389. double yAxis = feature.getRadiusY(size);
  390. var a = math.max(xAxis, yAxis);
  391. var b = math.min(xAxis, yAxis);
  392. var value = math.pi / 6.0 * a * b * b;
  393. updateFloatValue(value, unit: VidUsUnit.cm3, useRound: true);
  394. }
  395. }
  396. class EllipseFeatureForRoiCopy extends EllipseFeature {
  397. EllipseFeatureForRoiCopy(Ellipse refItem, DPoint point)
  398. : super(refItem, point);
  399. @override
  400. bool needRecordHistory = false; // 不记录历史
  401. }