This commit is contained in:
2025-04-18 14:03:42 +08:00
parent 6182dc2192
commit 73f8d2b968
16 changed files with 1904 additions and 261 deletions

View File

@ -16,54 +16,61 @@ namespace DHSoftware.Views
private Bitmap _currentImage;
private float _scale = 1.0f;
private PointF _offset = PointF.Empty;
private RectangleF _roiRect;
private PointF _roiStart;
private bool _isDrawing;
private Point _dragStart;
private bool _isDragging;
private Pen _roiPen = new Pen(Color.Red, 2);
private readonly object _imageLock = new object();
#endregion
#region
#region
public Bitmap Image
{
get => _currentImage;
get
{
lock (_imageLock)
{
return _currentImage?.Clone() as Bitmap;
}
}
set
{
// 记录旧状态
var oldSize = _currentImage?.Size ?? Size.Empty;
var oldScale = _scale;
var oldOffset = _offset;
Bitmap newImage = value?.Clone() as Bitmap;
Bitmap oldImageToDispose = null;
_currentImage?.Dispose();
_currentImage = value;
if (_currentImage != null)
lock (_imageLock)
{
if (_currentImage.Size != oldSize)
// 交换图像引用
oldImageToDispose = _currentImage;
_currentImage = newImage;
if (_currentImage != null)
{
// 尺寸不同时重置ROI、自动适配
_roiRect = RectangleF.Empty;
AutoFit();
}
else
{
// 尺寸相同时:保留缩放和偏移
_scale = oldScale;
_offset = oldOffset;
ClampOffset();
if (oldImageToDispose?.Size != _currentImage.Size)
{
AutoFit();
}
else
{
ClampOffset();
}
}
}
pictureBox.Invalidate();
// 在锁外安全释放旧图像
if (oldImageToDispose != null)
{
// 使用BeginInvoke确保在UI线程释放资源
BeginInvoke(new Action(() =>
{
oldImageToDispose.Dispose();
oldImageToDispose = null;
}));
}
SafeInvalidate();
}
}
#endregion
public RectangleF CurrentROI => _roiRect;
#endregion
public ImageViewerControl()
{
InitializeComponents();
@ -73,15 +80,13 @@ namespace DHSoftware.Views
#region
private void InitializeComponents()
{
// 主显示区域
pictureBox = new PictureBox
{
Dock = DockStyle.Fill,
BackColor = Color.DarkGray,
Cursor = Cursors.Cross
Cursor = Cursors.Hand
};
// 状态栏
statusLabel = new Label
{
Dock = DockStyle.Bottom,
@ -91,7 +96,6 @@ namespace DHSoftware.Views
Font = new Font("Consolas", 10)
};
// 事件绑定
pictureBox.MouseDown += PictureBox_MouseDown;
pictureBox.MouseMove += PictureBox_MouseMove;
pictureBox.MouseUp += PictureBox_MouseUp;
@ -106,8 +110,8 @@ namespace DHSoftware.Views
{
typeof(PictureBox).GetMethod("SetStyle",
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance)
?.Invoke(pictureBox, new object[] {
System.Reflection.BindingFlags.Instance)?
.Invoke(pictureBox, new object[] {
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint,
true
@ -118,189 +122,225 @@ namespace DHSoftware.Views
#region
private void AutoFit()
{
if (_currentImage == null) return;
lock (_imageLock)
{
if (_currentImage == null) return;
const float marginRatio = 0.1f;
float marginWidth = Width * marginRatio;
float marginHeight = Height * marginRatio;
const float marginRatio = 0.1f;
float marginWidth = Width * marginRatio;
float marginHeight = Height * marginRatio;
_scale = Math.Min(
(Width - marginWidth * 2) / _currentImage.Width,
(Height - marginHeight * 2) / _currentImage.Height
);
_scale = Math.Min(
(Width - marginWidth * 2) / _currentImage.Width,
(Height - marginHeight * 2) / _currentImage.Height
);
_offset.X = marginWidth + (Width - marginWidth * 2 - _currentImage.Width * _scale) / 2;
_offset.Y = marginHeight + (Height - marginHeight * 2 - _currentImage.Height * _scale) / 2;
_offset.X = marginWidth + (Width - marginWidth * 2 - _currentImage.Width * _scale) / 2;
_offset.Y = marginHeight + (Height - marginHeight * 2 - _currentImage.Height * _scale) / 2;
ClampOffset();
}
}
private void PictureBox_Paint(object sender, PaintEventArgs e)
{
if (_currentImage == null) return;
Bitmap drawImage = null;
RectangleF destRect;
float scale;
PointF offset;
// 绘制图像
var destRect = new RectangleF(
_offset.X,
_offset.Y,
_currentImage.Width * _scale,
_currentImage.Height * _scale);
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
e.Graphics.DrawImage(_currentImage, destRect);
// 绘制ROI
if (!_roiRect.IsEmpty)
// 创建临时绘图参数
lock (_imageLock)
{
var displayRect = new RectangleF(
_roiRect.X * _scale + _offset.X,
_roiRect.Y * _scale + _offset.Y,
_roiRect.Width * _scale,
_roiRect.Height * _scale);
if (_currentImage == null) return;
using (var pen = new Pen(_roiPen.Color, _roiPen.Width / _scale))
{
e.Graphics.DrawRectangle(pen,
displayRect.X,
displayRect.Y,
displayRect.Width,
displayRect.Height);
}
// 创建绘图副本
drawImage = _currentImage.Clone() as Bitmap;
scale = _scale;
offset = _offset;
destRect = new RectangleF(
offset.X,
offset.Y,
drawImage.Width * scale,
drawImage.Height * scale);
}
try
{
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
e.Graphics.DrawImage(drawImage, destRect);
}
finally
{
drawImage?.Dispose();
}
}
#endregion
#region
#region
private void PictureBox_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left && _currentImage != null)
if (InvokeRequired) return;
lock (_imageLock)
{
_roiStart = ClampCoordinates(ConvertToImageCoords(e.Location));
_isDrawing = true;
}
else if (e.Button == MouseButtons.Right)
{
_dragStart = e.Location;
_isDragging = true;
if (_currentImage == null) return;
if (e.Button == MouseButtons.Left)
{
_dragStart = e.Location;
_isDragging = true;
}
}
}
private void PictureBox_MouseMove(object sender, MouseEventArgs e)
{
if (_isDragging)
{
_offset.X += e.X - _dragStart.X;
_offset.Y += e.Y - _dragStart.Y;
_dragStart = e.Location;
ClampOffset();
pictureBox.Invalidate();
}
else if (_isDrawing && _currentImage != null)
{
PointF current = ClampCoordinates(ConvertToImageCoords(e.Location));
_roiRect = new RectangleF(
Math.Min(_roiStart.X, current.X),
Math.Min(_roiStart.Y, current.Y),
Math.Abs(_roiStart.X - current.X),
Math.Abs(_roiStart.Y - current.Y)
);
pictureBox.Invalidate();
}
if (InvokeRequired) return;
UpdateStatus(e.Location);
lock (_imageLock)
{
if (_currentImage == null) return;
if (_isDragging)
{
_offset.X += e.X - _dragStart.X;
_offset.Y += e.Y - _dragStart.Y;
_dragStart = e.Location;
ClampOffset();
SafeInvalidate();
}
UpdateStatus(e.Location);
}
}
private void PictureBox_MouseUp(object sender, MouseEventArgs e)
{
_isDragging = false;
_isDrawing = false;
lock (_imageLock)
{
_isDragging = false;
}
}
private void PictureBox_MouseWheel(object sender, MouseEventArgs e)
{
if (_currentImage == null) return;
if (InvokeRequired) return;
PointF mousePos = e.Location;
PointF imgPosBefore = ConvertToImageCoords(mousePos);
lock (_imageLock)
{
if (_currentImage == null) return;
if (imgPosBefore.X < 0 || imgPosBefore.X > _currentImage.Width ||
imgPosBefore.Y < 0 || imgPosBefore.Y > _currentImage.Height) return;
PointF imgPos = ConvertToImageCoords(e.Location);
if (imgPos.X < 0 || imgPos.X > _currentImage.Width ||
imgPos.Y < 0 || imgPos.Y > _currentImage.Height)
return;
float zoom = e.Delta > 0 ? 1.1f : 0.9f;
float newScale = Math.Clamp(_scale * zoom, 0.1f, 10f);
float zoom = e.Delta > 0 ? 1.1f : 0.9f;
float newScale = Math.Clamp(_scale * zoom, 0.1f, 10f);
_offset.X = mousePos.X - imgPosBefore.X * newScale;
_offset.Y = mousePos.Y - imgPosBefore.Y * newScale;
_offset.X = e.Location.X - imgPos.X * newScale;
_offset.Y = e.Location.Y - imgPos.Y * newScale;
_scale = newScale;
ClampOffset();
pictureBox.Invalidate();
_scale = newScale;
ClampOffset();
SafeInvalidate();
}
}
#endregion
#region
private PointF ConvertToImageCoords(PointF mousePos)
private PointF ConvertToImageCoords(Point mousePos)
{
return new PointF(
(mousePos.X - _offset.X) / _scale,
(mousePos.Y - _offset.Y) / _scale);
}
private PointF ClampCoordinates(PointF point)
{
return new PointF(
Math.Max(0, Math.Min(_currentImage.Width, point.X)),
Math.Max(0, Math.Min(_currentImage.Height, point.Y)));
lock (_imageLock)
{
if (_currentImage == null) return PointF.Empty;
return new PointF(
(mousePos.X - _offset.X) / _scale,
(mousePos.Y - _offset.Y) / _scale);
}
}
private void ClampOffset()
{
if (_currentImage == null) return;
lock (_imageLock)
{
if (_currentImage == null) return;
float imgWidth = _currentImage.Width * _scale;
float imgHeight = _currentImage.Height * _scale;
float imgWidth = _currentImage.Width * _scale;
float imgHeight = _currentImage.Height * _scale;
if (imgWidth <= Width)
{
_offset.X = Math.Clamp(_offset.X, 0, Width - imgWidth);
}
else
{
_offset.X = Math.Clamp(_offset.X, Width - imgWidth, 0);
}
_offset.X = Math.Clamp(_offset.X,
imgWidth > Width ? Width - imgWidth : 0,
imgWidth > Width ? 0 : Width - imgWidth);
if (imgHeight <= Height)
{
_offset.Y = Math.Clamp(_offset.Y, 0, Height - imgHeight);
}
else
{
_offset.Y = Math.Clamp(_offset.Y, Height - imgHeight, 0);
_offset.Y = Math.Clamp(_offset.Y,
imgHeight > Height ? Height - imgHeight : 0,
imgHeight > Height ? 0 : Height - imgHeight);
}
}
private void UpdateStatus(Point mousePos)
{
if (_currentImage == null) return;
if (InvokeRequired)
{
BeginInvoke(new Action<Point>(UpdateStatus), mousePos);
return;
}
PointF imgPos = ConvertToImageCoords(mousePos);
bool inImage = imgPos.X >= 0 && imgPos.X <= _currentImage.Width &&
imgPos.Y >= 0 && imgPos.Y <= _currentImage.Height;
lock (_imageLock)
{
if (_currentImage == null)
{
statusLabel.Text = "无有效图像";
return;
}
string roiInfo = _roiRect.IsEmpty ?
"未选择区域" :
$"选区: X={_roiRect.X:0} Y={_roiRect.Y:0} {_roiRect.Width:0}x{_roiRect.Height:0}";
PointF imgPos = ConvertToImageCoords(mousePos);
bool inImage = imgPos.X >= 0 && imgPos.X <= _currentImage.Width &&
imgPos.Y >= 0 && imgPos.Y <= _currentImage.Height;
statusLabel.Text = inImage ?
$"坐标: ({imgPos.X:0}, {imgPos.Y:0}) | 缩放: {_scale * 100:0}% | {roiInfo}" :
$"图像尺寸: {_currentImage.Width}x{_currentImage.Height} | 缩放: {_scale * 100:0}% | {roiInfo}";
statusLabel.Text = inImage ?
$"坐标: ({imgPos.X:F1}, {imgPos.Y:F1}) | 缩放: {_scale * 100:0}%" :
$"图像尺寸: {_currentImage.Width}x{_currentImage.Height} | 缩放: {_scale * 100:0}%";
}
}
public void ClearROI()
private void SafeInvalidate()
{
_roiRect = RectangleF.Empty;
pictureBox.Invalidate(); // 触发重绘
UpdateStatus(Point.Empty); // 更新状态栏
if (InvokeRequired)
{
BeginInvoke(new Action(pictureBox.Invalidate));
}
else
{
pictureBox.Invalidate();
}
}
#endregion
public Bitmap GetCurrentSnapshot()
{
lock (_imageLock)
{
// 返回深拷贝防止原始图像被修改
return _currentImage?.Clone() as Bitmap;
}
}
#endregion
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
lock (_imageLock)
{
_currentImage?.Dispose();
_currentImage = null;
}
base.Dispose(disposing);
}
}
}