using CSScripting; using OpenCvSharp; using OpenCvSharp.Extensions; using OpenCvSharp.WpfExtensions; using OxyPlot; using OxyPlot.Axes; using OxyPlot.Series; using Prism.Commands; using Prism.Mvvm; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; using System.Windows.Threading; using TeamAAS_VP.Core; using TeamAAS_VP.Enums; using TeamAAS_VP.Models; using TeamAAS_VP.ViewModels; using TeamAAS_VP.ViewModels.DebugMod; namespace TeamAAS_VP.Views.DebugMod { public partial class FocusAnalyzer : UserControl { private FocusAnalyzerViewModel ViewModel ; private System.Windows.Point _startPoint; private System.Windows.Point _lastPoint; private bool _isDragging = false; private DragHandleType _currentHandle = DragHandleType.None; private double _originalAngle = 0; public FocusAnalyzer() { InitializeComponent(); // 订阅鼠标事件 RoiCanvas.MouseLeftButtonDown += RoiCanvas_MouseLeftButtonDown; RoiCanvas.MouseMove += RoiCanvas_MouseMove; RoiCanvas.MouseLeftButtonUp += RoiCanvas_MouseLeftButtonUp; // 当Canvas或Image大小变化时,重新映射ROI以保持与图像同步 RoiCanvas.SizeChanged += RoiCanvas_SizeChanged; ImageDisplay.SizeChanged += RoiCanvas_SizeChanged; this.SizeChanged += RoiCanvas_SizeChanged; // 订阅ViewModel的ROI变化事件 Loaded += FocusAnalyzerControl_Loaded; } private void FocusAnalyzerControl_Loaded(object sender, RoutedEventArgs e) { if(ViewModel==null) ViewModel = DataContext as FocusAnalyzerViewModel; ; if (ViewModel != null) { ViewModel.PropertyChanged += ViewModel_PropertyChanged; // 设置ROI到图像坐标的转换器 ViewModel.RoiToImageRectConverter = ConvertRoiToImageRect; } } private void RoiCanvas_SizeChanged(object sender, SizeChangedEventArgs e) { // When the canvas or window resizes, remap existing ROI so it stays aligned to the same image area if (ViewModel == null || ViewModel.CurrentRoi == null || !ViewModel.CurrentRoi.IsValid) return; if (ViewModel.RoiToImageRectConverter == null) return; var imgRect = ViewModel.RoiToImageRectConverter(ViewModel.CurrentRoi); if (!imgRect.HasValue) return; // Convert image pixel rect back to canvas coordinates and update CurrentRoi var newRoi = ConvertImageRectToCanvasRoi(imgRect.Value); if (newRoi != null) { // Preserve angle double angle = ViewModel.CurrentRoi.Angle; ViewModel.CurrentRoi.X = newRoi.X; ViewModel.CurrentRoi.Y = newRoi.Y; ViewModel.CurrentRoi.Width = newRoi.Width; ViewModel.CurrentRoi.Height = newRoi.Height; ViewModel.CurrentRoi.Angle = angle; } } private TeamAAS_VP.Models.RoiModel ConvertImageRectToCanvasRoi(OpenCvSharp.Rect imgRect) { if (ViewModel?.CurrentImage == null) return null; var bmp = ViewModel.CurrentImage as System.Windows.Media.Imaging.BitmapSource; if (bmp == null || bmp.PixelWidth == 0 || bmp.PixelHeight == 0) return null; double canvasW = RoiCanvas.ActualWidth; double canvasH = RoiCanvas.ActualHeight; if (canvasW <= 0 || canvasH <= 0) return null; double imgPixelW = bmp.PixelWidth; double imgPixelH = bmp.PixelHeight; double imgRatio = imgPixelW / imgPixelH; double canvasRatio = canvasW / canvasH; double dispW, dispH; if (canvasRatio > imgRatio) { // canvas wider than image => fit height dispH = canvasH; dispW = dispH * imgRatio; } else { // fit width dispW = canvasW; dispH = dispW / imgRatio; } double offsetX = (canvasW - dispW) / 2.0; double offsetY = (canvasH - dispH) / 2.0; double scaleX = imgPixelW / dispW; double scaleY = imgPixelH / dispH; double canvasX = offsetX + imgRect.X / scaleX; double canvasY = offsetY + imgRect.Y / scaleY; double canvasWRect = imgRect.Width / scaleX; double canvasHRect = imgRect.Height / scaleY; return new TeamAAS_VP.Models.RoiModel { X = canvasX, Y = canvasY, Width = canvasWRect, Height = canvasHRect, Angle = 0 }; } private OpenCvSharp.Rect? ConvertRoiToImageRect(TeamAAS_VP.Models.RoiModel roi) { if (roi == null || !roi.IsValid || ViewModel?.CurrentImage == null) return null; try { var bmp = ViewModel.CurrentImage as System.Windows.Media.Imaging.BitmapSource; if (bmp == null || bmp.PixelWidth == 0 || bmp.PixelHeight == 0) return null; double canvasW = RoiCanvas.ActualWidth; double canvasH = RoiCanvas.ActualHeight; if (canvasW <= 0 || canvasH <= 0) return null; double imgPixelW = bmp.PixelWidth; double imgPixelH = bmp.PixelHeight; double imgRatio = imgPixelW / imgPixelH; double canvasRatio = canvasW / canvasH; double dispW, dispH; if (canvasRatio > imgRatio) { // canvas wider than image => fit height dispH = canvasH; dispW = dispH * imgRatio; } else { // fit width dispW = canvasW; dispH = dispW / imgRatio; } double offsetX = (canvasW - dispW) / 2.0; double offsetY = (canvasH - dispH) / 2.0; // ROI relative to displayed image area double rx = roi.X - offsetX; double ry = roi.Y - offsetY; // clamp to displayed image area if (rx + roi.Width <= 0 || ry + roi.Height <= 0) return null; double clampedX = Math.Max(0, rx); double clampedY = Math.Max(0, ry); double clampedW = Math.Min(roi.Width - (clampedX - rx), dispW - clampedX); double clampedH = Math.Min(roi.Height - (clampedY - ry), dispH - clampedY); if (clampedW <= 0 || clampedH <= 0) return null; double scaleX = imgPixelW / dispW; double scaleY = imgPixelH / dispH; int ix = (int)(clampedX * scaleX); int iy = (int)(clampedY * scaleY); int iw = Math.Max(1, (int)(clampedW * scaleX)); int ih = Math.Max(1, (int)(clampedH * scaleY)); // clamp to image bounds ix = Math.Max(0, Math.Min(ix, (int)imgPixelW - 1)); iy = Math.Max(0, Math.Min(iy, (int)imgPixelH - 1)); iw = Math.Min(iw, (int)imgPixelW - ix); ih = Math.Min(ih, (int)imgPixelH - iy); return new OpenCvSharp.Rect(ix, iy, iw, ih); } catch { return null; } } private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { if (e.PropertyName == nameof(ViewModel.CurrentRoi)) { UpdateRoiVisual(); } } private void RoiCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { if (ViewModel?.CurrentImage == null) return; _startPoint = e.GetPosition(RoiCanvas); _lastPoint = _startPoint; _isDragging = true; // 检测点击的是哪个控制点 _currentHandle = GetHandleAtPoint(_startPoint); if (_currentHandle == DragHandleType.None && ViewModel.RoiMode == RoiOperationMode.Draw) { // 开始绘制新的ROI ViewModel.CurrentRoi.X = _startPoint.X; ViewModel.CurrentRoi.Y = _startPoint.Y; ViewModel.CurrentRoi.Width = 0; ViewModel.CurrentRoi.Height = 0; ViewModel.CurrentRoi.Angle = 0; } else if (_currentHandle == DragHandleType.Rotate) { _originalAngle = ViewModel.CurrentRoi.Angle; } RoiCanvas.CaptureMouse(); } private void RoiCanvas_MouseMove(object sender, MouseEventArgs e) { if (!_isDragging || ViewModel?.CurrentImage == null) return; System.Windows.Point currentPoint = e.GetPosition(RoiCanvas); double deltaX = currentPoint.X - _lastPoint.X; double deltaY = currentPoint.Y - _lastPoint.Y; var roi = ViewModel.CurrentRoi; switch (ViewModel.RoiMode) { case RoiOperationMode.Draw: // 绘制模式 if (_currentHandle == DragHandleType.None) { roi.Width = Math.Abs(currentPoint.X - _startPoint.X); roi.Height = Math.Abs(currentPoint.Y - _startPoint.Y); roi.X = Math.Min(_startPoint.X, currentPoint.X); roi.Y = Math.Min(_startPoint.Y, currentPoint.Y); } break; case RoiOperationMode.Move: // 移动模式 if (IsPointInRoi(_lastPoint)) { roi.X += deltaX; roi.Y += deltaY; } break; case RoiOperationMode.Resize: // 调整大小模式 HandleResize(currentPoint, deltaX, deltaY); break; case RoiOperationMode.Rotate: // 旋转模式 if (_currentHandle == DragHandleType.Rotate) { HandleRotation(currentPoint); } break; } // 通用:如果点击了控制点,执行相应操作 if (_currentHandle != DragHandleType.None) { HandleControlPoint(currentPoint, deltaX, deltaY); } _lastPoint = currentPoint; UpdateRoiVisual(); } private void RoiCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { _isDragging = false; _currentHandle = DragHandleType.None; RoiCanvas.ReleaseMouseCapture(); } private DragHandleType GetHandleAtPoint(System.Windows.Point point) { const double tolerance = 10; if (!ViewModel.CurrentRoi.IsValid) return DragHandleType.None; var roi = ViewModel.CurrentRoi; double centerX = roi.X + roi.Width / 2; double centerY = roi.Y + roi.Height / 2; // 检查角点 if (IsPointNear(point, new System.Windows.Point(roi.X, roi.Y), tolerance)) return DragHandleType.TopLeft; if (IsPointNear(point, new System.Windows.Point(roi.X + roi.Width, roi.Y), tolerance)) return DragHandleType.TopRight; if (IsPointNear(point, new System.Windows.Point(roi.X, roi.Y + roi.Height), tolerance)) return DragHandleType.BottomLeft; if (IsPointNear(point, new System.Windows.Point(roi.X + roi.Width, roi.Y + roi.Height), tolerance)) return DragHandleType.BottomRight; // 检查边中点 if (IsPointNear(point, new System.Windows.Point(centerX, roi.Y), tolerance)) return DragHandleType.Top; if (IsPointNear(point, new System.Windows.Point(roi.X + roi.Width, centerY), tolerance)) return DragHandleType.Right; if (IsPointNear(point, new System.Windows.Point(centerX, roi.Y + roi.Height), tolerance)) return DragHandleType.Bottom; if (IsPointNear(point, new System.Windows.Point(roi.X, centerY), tolerance)) return DragHandleType.Left; // 检查旋转控制点(在顶部上方) if (IsPointNear(point, new System.Windows.Point(centerX, roi.Y - 30), tolerance)) return DragHandleType.Rotate; return DragHandleType.None; } private bool IsPointNear(System.Windows.Point p1, System.Windows.Point p2, double tolerance) { double distance = Math.Sqrt(Math.Pow(p1.X - p2.X, 2) + Math.Pow(p1.Y - p2.Y, 2)); return distance <= tolerance; } private bool IsPointInRoi(System.Windows.Point point) { var roi = ViewModel.CurrentRoi; if (!roi.IsValid) return false; return point.X >= roi.X && point.X <= roi.X + roi.Width && point.Y >= roi.Y && point.Y <= roi.Y + roi.Height; } private void HandleResize(System.Windows.Point currentPoint, double deltaX, double deltaY) { // 根据当前控制点调整ROI大小(在HandleControlPoint中处理) } private void HandleRotation(System.Windows.Point currentPoint) { var roi = ViewModel.CurrentRoi; double centerX = roi.X + roi.Width / 2; double centerY = roi.Y + roi.Height / 2; // 计算旋转角度 double angle1 = Math.Atan2(_startPoint.Y - centerY, _startPoint.X - centerX); double angle2 = Math.Atan2(currentPoint.Y - centerY, currentPoint.X - centerX); double deltaAngle = (angle2 - angle1) * 180 / Math.PI; roi.Angle = _originalAngle + deltaAngle; // 限制角度范围 -180 到 180 while (roi.Angle > 180) roi.Angle -= 360; while (roi.Angle < -180) roi.Angle += 360; } private void HandleControlPoint(System.Windows.Point currentPoint, double deltaX, double deltaY) { var roi = ViewModel.CurrentRoi; switch (_currentHandle) { case DragHandleType.TopLeft: roi.Width -= deltaX; roi.Height -= deltaY; roi.X += deltaX; roi.Y += deltaY; break; case DragHandleType.TopRight: roi.Width += deltaX; roi.Height -= deltaY; roi.Y += deltaY; break; case DragHandleType.BottomLeft: roi.Width -= deltaX; roi.Height += deltaY; roi.X += deltaX; break; case DragHandleType.BottomRight: roi.Width += deltaX; roi.Height += deltaY; break; case DragHandleType.Top: roi.Height -= deltaY; roi.Y += deltaY; break; case DragHandleType.Bottom: roi.Height += deltaY; break; case DragHandleType.Left: roi.Width -= deltaX; roi.X += deltaX; break; case DragHandleType.Right: roi.Width += deltaX; break; case DragHandleType.Rotate: HandleRotation(currentPoint); break; } // 确保宽度和高度为正值 if (roi.Width < 10) { roi.X -= (10 - roi.Width); roi.Width = 10; } if (roi.Height < 10) { roi.Y -= (10 - roi.Height); roi.Height = 10; } } private void UpdateRoiVisual() { if (ViewModel?.CurrentRoi == null || !ViewModel.CurrentRoi.IsValid) { RoiRectangle.Visibility = Visibility.Collapsed; HideAllHandles(); return; } var roi = ViewModel.CurrentRoi; // 更新ROI矩形 RoiRectangle.Visibility = Visibility.Visible; Canvas.SetLeft(RoiRectangle, roi.X); Canvas.SetTop(RoiRectangle, roi.Y); RoiRectangle.Width = roi.Width; RoiRectangle.Height = roi.Height; // 更新控制点位置 UpdateHandlePositions(roi); } private void UpdateHandlePositions(Models.RoiModel roi) { double centerX = roi.X + roi.Width / 2; double centerY = roi.Y + roi.Height / 2; // 角点 SetHandlePosition(TopLeftHandle, roi.X, roi.Y); SetHandlePosition(TopRightHandle, roi.X + roi.Width, roi.Y); SetHandlePosition(BottomLeftHandle, roi.X, roi.Y + roi.Height); SetHandlePosition(BottomRightHandle, roi.X + roi.Width, roi.Y + roi.Height); // 边中点 SetHandlePosition(TopHandle, centerX, roi.Y); SetHandlePosition(RightHandle, roi.X + roi.Width, centerY); SetHandlePosition(BottomHandle, centerX, roi.Y + roi.Height); SetHandlePosition(LeftHandle, roi.X, centerY); // 旋转控制点 SetHandlePosition(RotateHandle, centerX, roi.Y - 30); ShowAllHandles(); } private void SetHandlePosition(UIElement handle, double x, double y) { if (handle is Ellipse ellipse) { Canvas.SetLeft(handle, x - ellipse.Width / 2); Canvas.SetTop(handle, y - ellipse.Height / 2); } else if (handle is Path path) { Canvas.SetLeft(handle, x - 5); Canvas.SetTop(handle, y - 5); } handle.Visibility = Visibility.Visible; } private void ShowAllHandles() { TopLeftHandle.Visibility = Visibility.Visible; TopRightHandle.Visibility = Visibility.Visible; BottomLeftHandle.Visibility = Visibility.Visible; BottomRightHandle.Visibility = Visibility.Visible; TopHandle.Visibility = Visibility.Visible; RightHandle.Visibility = Visibility.Visible; BottomHandle.Visibility = Visibility.Visible; LeftHandle.Visibility = Visibility.Visible; RotateHandle.Visibility = Visibility.Visible; } private void HideAllHandles() { TopLeftHandle.Visibility = Visibility.Collapsed; TopRightHandle.Visibility = Visibility.Collapsed; BottomLeftHandle.Visibility = Visibility.Collapsed; BottomRightHandle.Visibility = Visibility.Collapsed; TopHandle.Visibility = Visibility.Collapsed; RightHandle.Visibility = Visibility.Collapsed; BottomHandle.Visibility = Visibility.Collapsed; LeftHandle.Visibility = Visibility.Collapsed; RotateHandle.Visibility = Visibility.Collapsed; } } }