气泡测量:工具面板UI、BubbleMeasure模式、矩形ROI绘制/拖动/右下角调整大小、工具切换联动

This commit is contained in:
李伟
2026-04-27 11:32:06 +08:00
parent e7ae7085df
commit 0c8d8afc51
7 changed files with 463 additions and 2 deletions
@@ -370,6 +370,28 @@ namespace XP.ImageProcessing.RoiControl.Controls
private Point? _bgaPendingCenter; // 等待第二次点击定半径
private Ellipse _bgaPendingDot;
// 气泡测量状态
public enum BubbleSubTool { Roi, Wand, Brush, Eraser }
private BubbleSubTool _bubbleTool = BubbleSubTool.Roi;
private Rectangle _bubbleRoiRect;
private Ellipse _bubbleRoiHandle; // 右下角调整手柄
private Rect? _bubbleRoi;
private Point? _bubbleRoiStart;
private bool _bubbleRoiDragging;
private bool _bubbleRoiMoving; // 拖动整个 ROI
private bool _bubbleRoiResizing; // 右下角调整大小
private Point _bubbleRoiDragOffset;
private Image _bubbleMaskImage;
private System.Windows.Media.Imaging.WriteableBitmap _bubbleMask;
private int _bubbleThreshold = 128;
private int _bubbleBrushSize = 5;
private bool _bubbleBrushDragging;
public void SetBubbleTool(BubbleSubTool tool) => _bubbleTool = tool;
public void SetBubbleThreshold(int val) => _bubbleThreshold = val;
public void SetBubbleBrushSize(int val) => _bubbleBrushSize = val;
public Rect? BubbleRoi => _bubbleRoi;
// 拖拽状态
private Ellipse _mDraggingDot;
private object _mDraggingOwner;
@@ -448,6 +470,7 @@ namespace XP.ImageProcessing.RoiControl.Controls
HandleFillRateClick(pos);
else if (CurrentMeasureMode == Models.MeasureMode.BgaVoid)
HandleBgaVoidClick(pos);
// BubbleMeasure 的点击在 MouseDown/Move/Up 中处理(拖拽画 ROI 和画笔)
}
// ── 点点距 ──
@@ -1084,6 +1107,125 @@ namespace XP.ImageProcessing.RoiControl.Controls
}
}
// ── 气泡测量辅助 ──
private void EnsureBubbleRoiVisuals()
{
EnsureMeasureOverlay();
if (_bubbleRoiRect == null)
{
_bubbleRoiRect = new Rectangle
{
Stroke = Brushes.Red,
StrokeThickness = 1.5,
Fill = Brushes.Transparent,
Visibility = Visibility.Collapsed,
IsHitTestVisible = false
};
_measureOverlay.Children.Add(_bubbleRoiRect);
}
if (_bubbleRoiHandle == null)
{
_bubbleRoiHandle = new Ellipse
{
Width = 10, Height = 10,
Fill = Brushes.Red, Stroke = Brushes.White, StrokeThickness = 1,
Cursor = Cursors.SizeNWSE,
Visibility = Visibility.Collapsed,
IsHitTestVisible = false // 命中测试由 MouseDown 中的距离判断处理
};
_measureOverlay.Children.Add(_bubbleRoiHandle);
}
}
private void SyncBubbleRoiVisuals()
{
if (_bubbleRoiRect == null || !_bubbleRoi.HasValue) return;
var r = _bubbleRoi.Value;
Canvas.SetLeft(_bubbleRoiRect, r.X);
Canvas.SetTop(_bubbleRoiRect, r.Y);
_bubbleRoiRect.Width = r.Width;
_bubbleRoiRect.Height = r.Height;
_bubbleRoiRect.Visibility = Visibility.Visible;
if (_bubbleRoiHandle != null)
{
Canvas.SetLeft(_bubbleRoiHandle, r.Right - _bubbleRoiHandle.Width / 2);
Canvas.SetTop(_bubbleRoiHandle, r.Bottom - _bubbleRoiHandle.Height / 2);
_bubbleRoiHandle.Visibility = Visibility.Visible;
}
}
private void InitBubbleMask()
{
if (!_bubbleRoi.HasValue) return;
int w = (int)CanvasWidth, h = (int)CanvasHeight;
if (w <= 0 || h <= 0) return;
_bubbleMask = new System.Windows.Media.Imaging.WriteableBitmap(w, h, 96, 96,
PixelFormats.Bgra32, null);
if (_bubbleMaskImage == null)
{
EnsureMeasureOverlay();
_bubbleMaskImage = new Image
{
IsHitTestVisible = false,
Opacity = 0.45,
Stretch = Stretch.Fill
};
_bubbleMaskImage.SetBinding(Image.WidthProperty, new System.Windows.Data.Binding("CanvasWidth") { Source = this });
_bubbleMaskImage.SetBinding(Image.HeightProperty, new System.Windows.Data.Binding("CanvasHeight") { Source = this });
_measureOverlay.Children.Add(_bubbleMaskImage);
}
_bubbleMaskImage.Source = _bubbleMask;
}
private void ApplyBrushAt(Point pos)
{
// 画笔/橡皮:暂时空实现,下一步实现
}
private void UpdateBubbleResult()
{
// 计算空隙率:暂时空实现,下一步实现
}
/// <summary>清除气泡测量所有状态</summary>
public void ClearBubbleMeasure()
{
if (_measureOverlay != null)
{
if (_bubbleRoiRect != null) { _measureOverlay.Children.Remove(_bubbleRoiRect); _bubbleRoiRect = null; }
if (_bubbleRoiHandle != null) { _measureOverlay.Children.Remove(_bubbleRoiHandle); _bubbleRoiHandle = null; }
if (_bubbleMaskImage != null) { _measureOverlay.Children.Remove(_bubbleMaskImage); _bubbleMaskImage = null; }
}
_bubbleRoi = null;
_bubbleMask = null;
_bubbleRoiStart = null;
_bubbleRoiDragging = false;
_bubbleRoiMoving = false;
_bubbleRoiResizing = false;
_bubbleBrushDragging = false;
_bubbleTool = BubbleSubTool.Roi;
}
// 气泡工具切换事件
public static readonly RoutedEvent BubbleToolChangedEvent =
EventManager.RegisterRoutedEvent(nameof(BubbleToolChanged), RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(PolygonRoiCanvas));
public event RoutedEventHandler BubbleToolChanged
{
add { AddHandler(BubbleToolChangedEvent, value); }
remove { RemoveHandler(BubbleToolChangedEvent, value); }
}
private void RaiseBubbleToolChanged()
{
RaiseEvent(new RoutedEventArgs(BubbleToolChangedEvent));
}
// ── 重编号 ──
private void RenumberAll()
@@ -1354,6 +1496,57 @@ namespace XP.ImageProcessing.RoiControl.Controls
private void Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// 气泡测量模式:ROI 拖拽 / 画笔涂抹
if (CurrentMeasureMode == Models.MeasureMode.BubbleMeasure)
{
var pos = e.GetPosition(mainCanvas);
if (_bubbleTool == BubbleSubTool.Roi)
{
// 检查是否点击了右下角手柄
if (_bubbleRoiHandle != null && _bubbleRoi.HasValue)
{
var hx = Canvas.GetLeft(_bubbleRoiHandle) + _bubbleRoiHandle.Width / 2;
var hy = Canvas.GetTop(_bubbleRoiHandle) + _bubbleRoiHandle.Height / 2;
if (Math.Abs(pos.X - hx) < 10 && Math.Abs(pos.Y - hy) < 10)
{
_bubbleRoiResizing = true;
mainCanvas.CaptureMouse();
e.Handled = true;
return;
}
}
// 检查是否点击了 ROI 内部(拖动)
if (_bubbleRoi.HasValue && _bubbleRoi.Value.Contains(pos))
{
_bubbleRoiMoving = true;
_bubbleRoiDragOffset = new Point(pos.X - _bubbleRoi.Value.X, pos.Y - _bubbleRoi.Value.Y);
mainCanvas.CaptureMouse();
e.Handled = true;
return;
}
// 没有 ROI 时才画新的,有 ROI 时点击外部不拦截(让图像正常拖动)
if (!_bubbleRoi.HasValue)
{
_bubbleRoiStart = pos;
_bubbleRoiDragging = true;
EnsureBubbleRoiVisuals();
mainCanvas.CaptureMouse();
e.Handled = true;
return;
}
// 有 ROI 但点击了外部 → 不拦截,走正常拖动逻辑
}
if ((_bubbleTool == BubbleSubTool.Brush || _bubbleTool == BubbleSubTool.Eraser) && _bubbleRoi.HasValue)
{
_bubbleBrushDragging = true;
ApplyBrushAt(pos);
mainCanvas.CaptureMouse();
e.Handled = true;
return;
}
// 魔棒在 MouseUpCanvasClicked)中处理
}
lastMousePosition = e.GetPosition(imageDisplayGrid);
isDragging = false;
mainCanvas.CaptureMouse();
@@ -1361,6 +1554,54 @@ namespace XP.ImageProcessing.RoiControl.Controls
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
// 气泡测量:ROI 拖拽画新矩形
if (_bubbleRoiDragging && _bubbleRoiStart.HasValue && _bubbleRoiRect != null)
{
var pos = e.GetPosition(mainCanvas);
double x = Math.Min(_bubbleRoiStart.Value.X, pos.X);
double y = Math.Min(_bubbleRoiStart.Value.Y, pos.Y);
double w = Math.Abs(pos.X - _bubbleRoiStart.Value.X);
double h = Math.Abs(pos.Y - _bubbleRoiStart.Value.Y);
Canvas.SetLeft(_bubbleRoiRect, x);
Canvas.SetTop(_bubbleRoiRect, y);
_bubbleRoiRect.Width = w;
_bubbleRoiRect.Height = h;
_bubbleRoiRect.Visibility = Visibility.Visible;
if (_bubbleRoiHandle != null) _bubbleRoiHandle.Visibility = Visibility.Collapsed;
e.Handled = true;
return;
}
// 气泡测量:拖动 ROI
if (_bubbleRoiMoving && _bubbleRoi.HasValue)
{
var pos = e.GetPosition(mainCanvas);
double nx = pos.X - _bubbleRoiDragOffset.X;
double ny = pos.Y - _bubbleRoiDragOffset.Y;
_bubbleRoi = new Rect(nx, ny, _bubbleRoi.Value.Width, _bubbleRoi.Value.Height);
SyncBubbleRoiVisuals();
e.Handled = true;
return;
}
// 气泡测量:右下角调整大小
if (_bubbleRoiResizing && _bubbleRoi.HasValue)
{
var pos = e.GetPosition(mainCanvas);
double w = Math.Max(20, pos.X - _bubbleRoi.Value.X);
double h = Math.Max(20, pos.Y - _bubbleRoi.Value.Y);
_bubbleRoi = new Rect(_bubbleRoi.Value.X, _bubbleRoi.Value.Y, w, h);
SyncBubbleRoiVisuals();
e.Handled = true;
return;
}
// 气泡测量:画笔/橡皮拖拽
if (_bubbleBrushDragging && _bubbleRoi.HasValue)
{
var pos = e.GetPosition(mainCanvas);
ApplyBrushAt(pos);
e.Handled = true;
return;
}
if (e.LeftButton == MouseButtonState.Pressed && mainCanvas.IsMouseCaptured)
{
Point currentPosition = e.GetPosition(imageDisplayGrid);
@@ -1378,6 +1619,49 @@ namespace XP.ImageProcessing.RoiControl.Controls
private void Canvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
// 气泡测量:ROI 拖拽完成
if (_bubbleRoiDragging && _bubbleRoiStart.HasValue)
{
var pos = e.GetPosition(mainCanvas);
double x = Math.Min(_bubbleRoiStart.Value.X, pos.X);
double y = Math.Min(_bubbleRoiStart.Value.Y, pos.Y);
double w = Math.Abs(pos.X - _bubbleRoiStart.Value.X);
double h = Math.Abs(pos.Y - _bubbleRoiStart.Value.Y);
_bubbleRoiDragging = false;
_bubbleRoiStart = null;
mainCanvas.ReleaseMouseCapture();
if (w > 5 && h > 5)
{
_bubbleRoi = new Rect(x, y, w, h);
SyncBubbleRoiVisuals();
InitBubbleMask();
RaiseMeasureStatusChanged($"ROI 已设置: {w:F0}×{h:F0},可拖动/调整大小,或在面板切换魔棒工具");
}
e.Handled = true;
return;
}
// 气泡测量:拖动/调整完成
if (_bubbleRoiMoving || _bubbleRoiResizing)
{
_bubbleRoiMoving = false;
_bubbleRoiResizing = false;
mainCanvas.ReleaseMouseCapture();
if (_bubbleRoi.HasValue)
RaiseMeasureStatusChanged($"ROI: {_bubbleRoi.Value.Width:F0}×{_bubbleRoi.Value.Height:F0}");
e.Handled = true;
return;
}
// 气泡测量:画笔/橡皮松开
if (_bubbleBrushDragging)
{
_bubbleBrushDragging = false;
mainCanvas.ReleaseMouseCapture();
UpdateBubbleResult();
e.Handled = true;
return;
}
mainCanvas.ReleaseMouseCapture();
if (!isDragging)
@@ -7,6 +7,7 @@ namespace XP.ImageProcessing.RoiControl.Models
PointToLine,
Angle,
FillRate,
BgaVoid
BgaVoid,
BubbleMeasure
}
}