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)