From 7441526ed95f85907397e3e540ede8a791124fca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E4=BC=9F?= Date: Thu, 14 May 2026 13:48:56 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=8C=E7=81=B0=E5=BA=A6=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- XplorePlane/Events/MeasurementToolEvent.cs | 5 + XplorePlane/ViewModels/Main/MainViewModel.cs | 4 +- .../Views/Main/ViewportPanelView.xaml.cs | 171 ++++++++++++++++++ 3 files changed, 178 insertions(+), 2 deletions(-) diff --git a/XplorePlane/Events/MeasurementToolEvent.cs b/XplorePlane/Events/MeasurementToolEvent.cs index daa45a2..9964acb 100644 --- a/XplorePlane/Events/MeasurementToolEvent.cs +++ b/XplorePlane/Events/MeasurementToolEvent.cs @@ -25,4 +25,9 @@ namespace XplorePlane.Events /// 十字辅助线切换事件 /// public class ToggleCrosshairEvent : PubSubEvent { } + + /// + /// 行灰度分布切换事件 + /// + public class ToggleLineProfileEvent : PubSubEvent { } } diff --git a/XplorePlane/ViewModels/Main/MainViewModel.cs b/XplorePlane/ViewModels/Main/MainViewModel.cs index f71eeb9..151e895 100644 --- a/XplorePlane/ViewModels/Main/MainViewModel.cs +++ b/XplorePlane/ViewModels/Main/MainViewModel.cs @@ -961,8 +961,8 @@ namespace XplorePlane.ViewModels private void ExecuteGrayscale() { if (!CheckImageLoaded()) return; - _logger.Info("Grayscale conversion triggered."); - // TODO: 实现灰度转换逻辑 + _logger.Info("Line profile toggled."); + _eventAggregator.GetEvent().Publish(); } private void ExecuteSharpen() diff --git a/XplorePlane/Views/Main/ViewportPanelView.xaml.cs b/XplorePlane/Views/Main/ViewportPanelView.xaml.cs index 763d2ab..50f21c6 100644 --- a/XplorePlane/Views/Main/ViewportPanelView.xaml.cs +++ b/XplorePlane/Views/Main/ViewportPanelView.xaml.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel; using System.IO; +using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Media; @@ -128,8 +129,178 @@ namespace XplorePlane.Views var vm = GetMainVm(); if (vm != null) vm.CursorInfoText = RoiCanvas.CursorInfo; }); + + // 行灰度分布 + try + { + var ea2 = ContainerLocator.Current?.Resolve(); + ea2?.GetEvent().Subscribe(() => + { + ToggleLineProfile(); + }, Prism.Events.ThreadOption.UIThread); + } + catch { } } + #region 行灰度分布 + + private bool _lineProfileEnabled; + private System.Windows.Shapes.Line _profileRefLine; // 透明命中区域 + private System.Windows.Shapes.Line _profileRefLineVisible; // 1px红线显示 + private System.Windows.Shapes.Polyline _profileCurve; + private double _profileLineY; + private bool _profileDragging; + + private void ToggleLineProfile() + { + _lineProfileEnabled = !_lineProfileEnabled; + var canvas = FindChildByName(RoiCanvas, "mainCanvas"); + if (canvas == null) return; + + if (_lineProfileEnabled) + { + // 参考线默认在图像中间 + _profileLineY = RoiCanvas.CanvasHeight / 2; + + // 创建参考线(红色水平线,可拖动) + // 用透明粗线作为命中区域,叠加1px红线显示 + _profileRefLine = new System.Windows.Shapes.Line + { + X1 = 0, + Y1 = _profileLineY, + X2 = RoiCanvas.CanvasWidth, + Y2 = _profileLineY, + Stroke = System.Windows.Media.Brushes.Transparent, + StrokeThickness = 7, // 上下3px命中区域 + IsHitTestVisible = true, + Cursor = System.Windows.Input.Cursors.SizeNS + }; + _profileRefLineVisible = new System.Windows.Shapes.Line + { + X1 = 0, + Y1 = _profileLineY, + X2 = RoiCanvas.CanvasWidth, + Y2 = _profileLineY, + Stroke = System.Windows.Media.Brushes.Red, + StrokeThickness = 1, + IsHitTestVisible = false + }; + _profileRefLine.MouseLeftButtonDown += ProfileLine_MouseDown; + _profileRefLine.MouseMove += ProfileLine_MouseMove; + _profileRefLine.MouseLeftButtonUp += ProfileLine_MouseUp; + canvas.Children.Add(_profileRefLineVisible); + canvas.Children.Add(_profileRefLine); + + // 创建灰度折线(固定显示在图像中间位置) + _profileCurve = new System.Windows.Shapes.Polyline + { + Stroke = System.Windows.Media.Brushes.Red, + StrokeThickness = 1, + IsHitTestVisible = false + }; + canvas.Children.Add(_profileCurve); + + UpdateLineProfile(); + SetStatus("行灰度分布:拖动红线改变采样行,再次点击按钮关闭"); + } + else + { + if (_profileRefLine != null) + { + canvas.Children.Remove(_profileRefLine); + _profileRefLine = null; + } + if (_profileRefLineVisible != null) + { + canvas.Children.Remove(_profileRefLineVisible); + _profileRefLineVisible = null; + } + if (_profileCurve != null) + { + canvas.Children.Remove(_profileCurve); + _profileCurve = null; + } + SetStatus("行灰度分布已关闭"); + } + } + + private void ProfileLine_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e) + { + _profileDragging = true; + _profileRefLine?.CaptureMouse(); + e.Handled = true; + } + + private void ProfileLine_MouseMove(object sender, System.Windows.Input.MouseEventArgs e) + { + if (!_profileDragging || _profileRefLine == null) return; + + var canvas = FindChildByName(RoiCanvas, "mainCanvas"); + if (canvas == null) return; + + var pos = e.GetPosition(canvas); + _profileLineY = Math.Clamp(pos.Y, 0, RoiCanvas.CanvasHeight - 1); + + _profileRefLine.Y1 = _profileLineY; + _profileRefLine.Y2 = _profileLineY; + _profileRefLineVisible.Y1 = _profileLineY; + _profileRefLineVisible.Y2 = _profileLineY; + + UpdateLineProfile(); + } + + private void ProfileLine_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e) + { + _profileDragging = false; + _profileRefLine?.ReleaseMouseCapture(); + e.Handled = true; + } + + private void UpdateLineProfile() + { + if (_profileCurve == null) return; + + // 从当前显示图像获取像素数据 + var viewportVm = DataContext as ViewportPanelViewModel; + var imageSource = viewportVm?.ImageSource as System.Windows.Media.Imaging.BitmapSource; + if (imageSource == null) return; + + int imgWidth = imageSource.PixelWidth; + int imgHeight = imageSource.PixelHeight; + int row = (int)Math.Clamp(_profileLineY, 0, imgHeight - 1); + + // 转为 Gray8 获取行像素 + System.Windows.Media.Imaging.BitmapSource gray8; + if (imageSource.Format != System.Windows.Media.PixelFormats.Gray8) + gray8 = new System.Windows.Media.Imaging.FormatConvertedBitmap( + imageSource, System.Windows.Media.PixelFormats.Gray8, null, 0); + else + gray8 = imageSource; + + byte[] rowPixels = new byte[imgWidth]; + int stride = imgWidth; + gray8.CopyPixels(new System.Windows.Int32Rect(0, row, imgWidth, 1), rowPixels, stride, 0); + + // 构建折线点集:折线固定显示在图像垂直中间位置 + // 参考线位置决定采样哪一行,折线位置固定在画布中间 + double canvasH = RoiCanvas.CanvasHeight; + double curveCenter = canvasH / 2.0; // 折线基线固定在图像中间 + double displayHeight = canvasH * 0.25; // 折线振幅为画布高度的25% + + var points = new System.Windows.Media.PointCollection(imgWidth); + for (int x = 0; x < imgWidth; x++) + { + double normalizedGray = rowPixels[x] / 255.0; + double y = curveCenter - normalizedGray * displayHeight; + points.Add(new System.Windows.Point(x, y)); + } + _profileCurve.Points = points; + + SetStatus($"行灰度分布 | Y={row} | 均值={rowPixels.Select(b => (double)b).Average():F1} | 最大={rowPixels.Max()} | 最小={rowPixels.Min()}"); + } + + #endregion + private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) { if (e.OldValue is INotifyPropertyChanged oldVm)