| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559 |
- 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;
- namespace TeamAAS_VP.Controls
- {
- public partial class FocusAnalyzerControl : UserControl
- {
- private FocusAnalyzerViewModel ViewModel => DataContext as FocusAnalyzerViewModel;
- private System.Windows.Point _startPoint;
- private System.Windows.Point _lastPoint;
- private bool _isDragging = false;
- private DragHandleType _currentHandle = DragHandleType.None;
- private double _originalAngle = 0;
- public FocusAnalyzerControl()
- {
- 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.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;
- }
- }
- }
|