FocusAnalyzerl.xaml.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. using CSScripting;
  2. using OpenCvSharp;
  3. using OpenCvSharp.Extensions;
  4. using OpenCvSharp.WpfExtensions;
  5. using OxyPlot;
  6. using OxyPlot.Axes;
  7. using OxyPlot.Series;
  8. using Prism.Commands;
  9. using Prism.Mvvm;
  10. using System;
  11. using System.Collections.Generic;
  12. using System.Collections.ObjectModel;
  13. using System.ComponentModel;
  14. using System.Linq;
  15. using System.Runtime.CompilerServices;
  16. using System.Threading.Tasks;
  17. using System.Windows;
  18. using System.Windows.Controls;
  19. using System.Windows.Input;
  20. using System.Windows.Media;
  21. using System.Windows.Media.Imaging;
  22. using System.Windows.Shapes;
  23. using System.Windows.Threading;
  24. using TeamAAS_VP.Core;
  25. using TeamAAS_VP.Enums;
  26. using TeamAAS_VP.Models;
  27. using TeamAAS_VP.ViewModels;
  28. using TeamAAS_VP.ViewModels.DebugMod;
  29. namespace TeamAAS_VP.Views.DebugMod
  30. {
  31. public partial class FocusAnalyzer : UserControl
  32. {
  33. private FocusAnalyzerViewModel ViewModel ;
  34. private System.Windows.Point _startPoint;
  35. private System.Windows.Point _lastPoint;
  36. private bool _isDragging = false;
  37. private DragHandleType _currentHandle = DragHandleType.None;
  38. private double _originalAngle = 0;
  39. public FocusAnalyzer()
  40. {
  41. InitializeComponent();
  42. // 订阅鼠标事件
  43. RoiCanvas.MouseLeftButtonDown += RoiCanvas_MouseLeftButtonDown;
  44. RoiCanvas.MouseMove += RoiCanvas_MouseMove;
  45. RoiCanvas.MouseLeftButtonUp += RoiCanvas_MouseLeftButtonUp;
  46. // 当Canvas或Image大小变化时,重新映射ROI以保持与图像同步
  47. RoiCanvas.SizeChanged += RoiCanvas_SizeChanged;
  48. ImageDisplay.SizeChanged += RoiCanvas_SizeChanged;
  49. this.SizeChanged += RoiCanvas_SizeChanged;
  50. // 订阅ViewModel的ROI变化事件
  51. Loaded += FocusAnalyzerControl_Loaded;
  52. }
  53. private void FocusAnalyzerControl_Loaded(object sender, RoutedEventArgs e)
  54. {
  55. if(ViewModel==null)
  56. ViewModel = DataContext as FocusAnalyzerViewModel; ;
  57. if (ViewModel != null)
  58. {
  59. ViewModel.PropertyChanged += ViewModel_PropertyChanged;
  60. // 设置ROI到图像坐标的转换器
  61. ViewModel.RoiToImageRectConverter = ConvertRoiToImageRect;
  62. }
  63. }
  64. private void RoiCanvas_SizeChanged(object sender, SizeChangedEventArgs e)
  65. {
  66. // When the canvas or window resizes, remap existing ROI so it stays aligned to the same image area
  67. if (ViewModel == null || ViewModel.CurrentRoi == null || !ViewModel.CurrentRoi.IsValid)
  68. return;
  69. if (ViewModel.RoiToImageRectConverter == null)
  70. return;
  71. var imgRect = ViewModel.RoiToImageRectConverter(ViewModel.CurrentRoi);
  72. if (!imgRect.HasValue)
  73. return;
  74. // Convert image pixel rect back to canvas coordinates and update CurrentRoi
  75. var newRoi = ConvertImageRectToCanvasRoi(imgRect.Value);
  76. if (newRoi != null)
  77. {
  78. // Preserve angle
  79. double angle = ViewModel.CurrentRoi.Angle;
  80. ViewModel.CurrentRoi.X = newRoi.X;
  81. ViewModel.CurrentRoi.Y = newRoi.Y;
  82. ViewModel.CurrentRoi.Width = newRoi.Width;
  83. ViewModel.CurrentRoi.Height = newRoi.Height;
  84. ViewModel.CurrentRoi.Angle = angle;
  85. }
  86. }
  87. private TeamAAS_VP.Models.RoiModel ConvertImageRectToCanvasRoi(OpenCvSharp.Rect imgRect)
  88. {
  89. if (ViewModel?.CurrentImage == null)
  90. return null;
  91. var bmp = ViewModel.CurrentImage as System.Windows.Media.Imaging.BitmapSource;
  92. if (bmp == null || bmp.PixelWidth == 0 || bmp.PixelHeight == 0)
  93. return null;
  94. double canvasW = RoiCanvas.ActualWidth;
  95. double canvasH = RoiCanvas.ActualHeight;
  96. if (canvasW <= 0 || canvasH <= 0)
  97. return null;
  98. double imgPixelW = bmp.PixelWidth;
  99. double imgPixelH = bmp.PixelHeight;
  100. double imgRatio = imgPixelW / imgPixelH;
  101. double canvasRatio = canvasW / canvasH;
  102. double dispW, dispH;
  103. if (canvasRatio > imgRatio)
  104. {
  105. // canvas wider than image => fit height
  106. dispH = canvasH;
  107. dispW = dispH * imgRatio;
  108. }
  109. else
  110. {
  111. // fit width
  112. dispW = canvasW;
  113. dispH = dispW / imgRatio;
  114. }
  115. double offsetX = (canvasW - dispW) / 2.0;
  116. double offsetY = (canvasH - dispH) / 2.0;
  117. double scaleX = imgPixelW / dispW;
  118. double scaleY = imgPixelH / dispH;
  119. double canvasX = offsetX + imgRect.X / scaleX;
  120. double canvasY = offsetY + imgRect.Y / scaleY;
  121. double canvasWRect = imgRect.Width / scaleX;
  122. double canvasHRect = imgRect.Height / scaleY;
  123. return new TeamAAS_VP.Models.RoiModel
  124. {
  125. X = canvasX,
  126. Y = canvasY,
  127. Width = canvasWRect,
  128. Height = canvasHRect,
  129. Angle = 0
  130. };
  131. }
  132. private OpenCvSharp.Rect? ConvertRoiToImageRect(TeamAAS_VP.Models.RoiModel roi)
  133. {
  134. if (roi == null || !roi.IsValid || ViewModel?.CurrentImage == null)
  135. return null;
  136. try
  137. {
  138. var bmp = ViewModel.CurrentImage as System.Windows.Media.Imaging.BitmapSource;
  139. if (bmp == null || bmp.PixelWidth == 0 || bmp.PixelHeight == 0)
  140. return null;
  141. double canvasW = RoiCanvas.ActualWidth;
  142. double canvasH = RoiCanvas.ActualHeight;
  143. if (canvasW <= 0 || canvasH <= 0)
  144. return null;
  145. double imgPixelW = bmp.PixelWidth;
  146. double imgPixelH = bmp.PixelHeight;
  147. double imgRatio = imgPixelW / imgPixelH;
  148. double canvasRatio = canvasW / canvasH;
  149. double dispW, dispH;
  150. if (canvasRatio > imgRatio)
  151. {
  152. // canvas wider than image => fit height
  153. dispH = canvasH;
  154. dispW = dispH * imgRatio;
  155. }
  156. else
  157. {
  158. // fit width
  159. dispW = canvasW;
  160. dispH = dispW / imgRatio;
  161. }
  162. double offsetX = (canvasW - dispW) / 2.0;
  163. double offsetY = (canvasH - dispH) / 2.0;
  164. // ROI relative to displayed image area
  165. double rx = roi.X - offsetX;
  166. double ry = roi.Y - offsetY;
  167. // clamp to displayed image area
  168. if (rx + roi.Width <= 0 || ry + roi.Height <= 0)
  169. return null;
  170. double clampedX = Math.Max(0, rx);
  171. double clampedY = Math.Max(0, ry);
  172. double clampedW = Math.Min(roi.Width - (clampedX - rx), dispW - clampedX);
  173. double clampedH = Math.Min(roi.Height - (clampedY - ry), dispH - clampedY);
  174. if (clampedW <= 0 || clampedH <= 0)
  175. return null;
  176. double scaleX = imgPixelW / dispW;
  177. double scaleY = imgPixelH / dispH;
  178. int ix = (int)(clampedX * scaleX);
  179. int iy = (int)(clampedY * scaleY);
  180. int iw = Math.Max(1, (int)(clampedW * scaleX));
  181. int ih = Math.Max(1, (int)(clampedH * scaleY));
  182. // clamp to image bounds
  183. ix = Math.Max(0, Math.Min(ix, (int)imgPixelW - 1));
  184. iy = Math.Max(0, Math.Min(iy, (int)imgPixelH - 1));
  185. iw = Math.Min(iw, (int)imgPixelW - ix);
  186. ih = Math.Min(ih, (int)imgPixelH - iy);
  187. return new OpenCvSharp.Rect(ix, iy, iw, ih);
  188. }
  189. catch
  190. {
  191. return null;
  192. }
  193. }
  194. private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
  195. {
  196. if (e.PropertyName == nameof(ViewModel.CurrentRoi))
  197. {
  198. UpdateRoiVisual();
  199. }
  200. }
  201. private void RoiCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
  202. {
  203. if (ViewModel?.CurrentImage == null)
  204. return;
  205. _startPoint = e.GetPosition(RoiCanvas);
  206. _lastPoint = _startPoint;
  207. _isDragging = true;
  208. // 检测点击的是哪个控制点
  209. _currentHandle = GetHandleAtPoint(_startPoint);
  210. if (_currentHandle == DragHandleType.None && ViewModel.RoiMode == RoiOperationMode.Draw)
  211. {
  212. // 开始绘制新的ROI
  213. ViewModel.CurrentRoi.X = _startPoint.X;
  214. ViewModel.CurrentRoi.Y = _startPoint.Y;
  215. ViewModel.CurrentRoi.Width = 0;
  216. ViewModel.CurrentRoi.Height = 0;
  217. ViewModel.CurrentRoi.Angle = 0;
  218. }
  219. else if (_currentHandle == DragHandleType.Rotate)
  220. {
  221. _originalAngle = ViewModel.CurrentRoi.Angle;
  222. }
  223. RoiCanvas.CaptureMouse();
  224. }
  225. private void RoiCanvas_MouseMove(object sender, MouseEventArgs e)
  226. {
  227. if (!_isDragging || ViewModel?.CurrentImage == null)
  228. return;
  229. System.Windows.Point currentPoint = e.GetPosition(RoiCanvas);
  230. double deltaX = currentPoint.X - _lastPoint.X;
  231. double deltaY = currentPoint.Y - _lastPoint.Y;
  232. var roi = ViewModel.CurrentRoi;
  233. switch (ViewModel.RoiMode)
  234. {
  235. case RoiOperationMode.Draw:
  236. // 绘制模式
  237. if (_currentHandle == DragHandleType.None)
  238. {
  239. roi.Width = Math.Abs(currentPoint.X - _startPoint.X);
  240. roi.Height = Math.Abs(currentPoint.Y - _startPoint.Y);
  241. roi.X = Math.Min(_startPoint.X, currentPoint.X);
  242. roi.Y = Math.Min(_startPoint.Y, currentPoint.Y);
  243. }
  244. break;
  245. case RoiOperationMode.Move:
  246. // 移动模式
  247. if (IsPointInRoi(_lastPoint))
  248. {
  249. roi.X += deltaX;
  250. roi.Y += deltaY;
  251. }
  252. break;
  253. case RoiOperationMode.Resize:
  254. // 调整大小模式
  255. HandleResize(currentPoint, deltaX, deltaY);
  256. break;
  257. case RoiOperationMode.Rotate:
  258. // 旋转模式
  259. if (_currentHandle == DragHandleType.Rotate)
  260. {
  261. HandleRotation(currentPoint);
  262. }
  263. break;
  264. }
  265. // 通用:如果点击了控制点,执行相应操作
  266. if (_currentHandle != DragHandleType.None)
  267. {
  268. HandleControlPoint(currentPoint, deltaX, deltaY);
  269. }
  270. _lastPoint = currentPoint;
  271. UpdateRoiVisual();
  272. }
  273. private void RoiCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
  274. {
  275. _isDragging = false;
  276. _currentHandle = DragHandleType.None;
  277. RoiCanvas.ReleaseMouseCapture();
  278. }
  279. private DragHandleType GetHandleAtPoint(System.Windows.Point point)
  280. {
  281. const double tolerance = 10;
  282. if (!ViewModel.CurrentRoi.IsValid)
  283. return DragHandleType.None;
  284. var roi = ViewModel.CurrentRoi;
  285. double centerX = roi.X + roi.Width / 2;
  286. double centerY = roi.Y + roi.Height / 2;
  287. // 检查角点
  288. if (IsPointNear(point, new System.Windows.Point(roi.X, roi.Y), tolerance))
  289. return DragHandleType.TopLeft;
  290. if (IsPointNear(point, new System.Windows.Point(roi.X + roi.Width, roi.Y), tolerance))
  291. return DragHandleType.TopRight;
  292. if (IsPointNear(point, new System.Windows.Point(roi.X, roi.Y + roi.Height), tolerance))
  293. return DragHandleType.BottomLeft;
  294. if (IsPointNear(point, new System.Windows.Point(roi.X + roi.Width, roi.Y + roi.Height), tolerance))
  295. return DragHandleType.BottomRight;
  296. // 检查边中点
  297. if (IsPointNear(point, new System.Windows.Point(centerX, roi.Y), tolerance))
  298. return DragHandleType.Top;
  299. if (IsPointNear(point, new System.Windows.Point(roi.X + roi.Width, centerY), tolerance))
  300. return DragHandleType.Right;
  301. if (IsPointNear(point, new System.Windows.Point(centerX, roi.Y + roi.Height), tolerance))
  302. return DragHandleType.Bottom;
  303. if (IsPointNear(point, new System.Windows.Point(roi.X, centerY), tolerance))
  304. return DragHandleType.Left;
  305. // 检查旋转控制点(在顶部上方)
  306. if (IsPointNear(point, new System.Windows.Point(centerX, roi.Y - 30), tolerance))
  307. return DragHandleType.Rotate;
  308. return DragHandleType.None;
  309. }
  310. private bool IsPointNear(System.Windows.Point p1, System.Windows.Point p2, double tolerance)
  311. {
  312. double distance = Math.Sqrt(Math.Pow(p1.X - p2.X, 2) + Math.Pow(p1.Y - p2.Y, 2));
  313. return distance <= tolerance;
  314. }
  315. private bool IsPointInRoi(System.Windows.Point point)
  316. {
  317. var roi = ViewModel.CurrentRoi;
  318. if (!roi.IsValid)
  319. return false;
  320. return point.X >= roi.X && point.X <= roi.X + roi.Width &&
  321. point.Y >= roi.Y && point.Y <= roi.Y + roi.Height;
  322. }
  323. private void HandleResize(System.Windows.Point currentPoint, double deltaX, double deltaY)
  324. {
  325. // 根据当前控制点调整ROI大小(在HandleControlPoint中处理)
  326. }
  327. private void HandleRotation(System.Windows.Point currentPoint)
  328. {
  329. var roi = ViewModel.CurrentRoi;
  330. double centerX = roi.X + roi.Width / 2;
  331. double centerY = roi.Y + roi.Height / 2;
  332. // 计算旋转角度
  333. double angle1 = Math.Atan2(_startPoint.Y - centerY, _startPoint.X - centerX);
  334. double angle2 = Math.Atan2(currentPoint.Y - centerY, currentPoint.X - centerX);
  335. double deltaAngle = (angle2 - angle1) * 180 / Math.PI;
  336. roi.Angle = _originalAngle + deltaAngle;
  337. // 限制角度范围 -180 到 180
  338. while (roi.Angle > 180) roi.Angle -= 360;
  339. while (roi.Angle < -180) roi.Angle += 360;
  340. }
  341. private void HandleControlPoint(System.Windows.Point currentPoint, double deltaX, double deltaY)
  342. {
  343. var roi = ViewModel.CurrentRoi;
  344. switch (_currentHandle)
  345. {
  346. case DragHandleType.TopLeft:
  347. roi.Width -= deltaX;
  348. roi.Height -= deltaY;
  349. roi.X += deltaX;
  350. roi.Y += deltaY;
  351. break;
  352. case DragHandleType.TopRight:
  353. roi.Width += deltaX;
  354. roi.Height -= deltaY;
  355. roi.Y += deltaY;
  356. break;
  357. case DragHandleType.BottomLeft:
  358. roi.Width -= deltaX;
  359. roi.Height += deltaY;
  360. roi.X += deltaX;
  361. break;
  362. case DragHandleType.BottomRight:
  363. roi.Width += deltaX;
  364. roi.Height += deltaY;
  365. break;
  366. case DragHandleType.Top:
  367. roi.Height -= deltaY;
  368. roi.Y += deltaY;
  369. break;
  370. case DragHandleType.Bottom:
  371. roi.Height += deltaY;
  372. break;
  373. case DragHandleType.Left:
  374. roi.Width -= deltaX;
  375. roi.X += deltaX;
  376. break;
  377. case DragHandleType.Right:
  378. roi.Width += deltaX;
  379. break;
  380. case DragHandleType.Rotate:
  381. HandleRotation(currentPoint);
  382. break;
  383. }
  384. // 确保宽度和高度为正值
  385. if (roi.Width < 10)
  386. {
  387. roi.X -= (10 - roi.Width);
  388. roi.Width = 10;
  389. }
  390. if (roi.Height < 10)
  391. {
  392. roi.Y -= (10 - roi.Height);
  393. roi.Height = 10;
  394. }
  395. }
  396. private void UpdateRoiVisual()
  397. {
  398. if (ViewModel?.CurrentRoi == null || !ViewModel.CurrentRoi.IsValid)
  399. {
  400. RoiRectangle.Visibility = Visibility.Collapsed;
  401. HideAllHandles();
  402. return;
  403. }
  404. var roi = ViewModel.CurrentRoi;
  405. // 更新ROI矩形
  406. RoiRectangle.Visibility = Visibility.Visible;
  407. Canvas.SetLeft(RoiRectangle, roi.X);
  408. Canvas.SetTop(RoiRectangle, roi.Y);
  409. RoiRectangle.Width = roi.Width;
  410. RoiRectangle.Height = roi.Height;
  411. // 更新控制点位置
  412. UpdateHandlePositions(roi);
  413. }
  414. private void UpdateHandlePositions(Models.RoiModel roi)
  415. {
  416. double centerX = roi.X + roi.Width / 2;
  417. double centerY = roi.Y + roi.Height / 2;
  418. // 角点
  419. SetHandlePosition(TopLeftHandle, roi.X, roi.Y);
  420. SetHandlePosition(TopRightHandle, roi.X + roi.Width, roi.Y);
  421. SetHandlePosition(BottomLeftHandle, roi.X, roi.Y + roi.Height);
  422. SetHandlePosition(BottomRightHandle, roi.X + roi.Width, roi.Y + roi.Height);
  423. // 边中点
  424. SetHandlePosition(TopHandle, centerX, roi.Y);
  425. SetHandlePosition(RightHandle, roi.X + roi.Width, centerY);
  426. SetHandlePosition(BottomHandle, centerX, roi.Y + roi.Height);
  427. SetHandlePosition(LeftHandle, roi.X, centerY);
  428. // 旋转控制点
  429. SetHandlePosition(RotateHandle, centerX, roi.Y - 30);
  430. ShowAllHandles();
  431. }
  432. private void SetHandlePosition(UIElement handle, double x, double y)
  433. {
  434. if (handle is Ellipse ellipse)
  435. {
  436. Canvas.SetLeft(handle, x - ellipse.Width / 2);
  437. Canvas.SetTop(handle, y - ellipse.Height / 2);
  438. }
  439. else if (handle is Path path)
  440. {
  441. Canvas.SetLeft(handle, x - 5);
  442. Canvas.SetTop(handle, y - 5);
  443. }
  444. handle.Visibility = Visibility.Visible;
  445. }
  446. private void ShowAllHandles()
  447. {
  448. TopLeftHandle.Visibility = Visibility.Visible;
  449. TopRightHandle.Visibility = Visibility.Visible;
  450. BottomLeftHandle.Visibility = Visibility.Visible;
  451. BottomRightHandle.Visibility = Visibility.Visible;
  452. TopHandle.Visibility = Visibility.Visible;
  453. RightHandle.Visibility = Visibility.Visible;
  454. BottomHandle.Visibility = Visibility.Visible;
  455. LeftHandle.Visibility = Visibility.Visible;
  456. RotateHandle.Visibility = Visibility.Visible;
  457. }
  458. private void HideAllHandles()
  459. {
  460. TopLeftHandle.Visibility = Visibility.Collapsed;
  461. TopRightHandle.Visibility = Visibility.Collapsed;
  462. BottomLeftHandle.Visibility = Visibility.Collapsed;
  463. BottomRightHandle.Visibility = Visibility.Collapsed;
  464. TopHandle.Visibility = Visibility.Collapsed;
  465. RightHandle.Visibility = Visibility.Collapsed;
  466. BottomHandle.Visibility = Visibility.Collapsed;
  467. LeftHandle.Visibility = Visibility.Collapsed;
  468. RotateHandle.Visibility = Visibility.Collapsed;
  469. }
  470. }
  471. }