Files
DHDHSoftware/DHSoftware/Views/ImageViewerControl.cs
T
2025-04-18 14:03:42 +08:00

346 lines
10 KiB
C#

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace DHSoftware.Views
{
public partial class ImageViewerControl : UserControl
{
#region 内部控件
private PictureBox pictureBox;
private Label statusLabel;
#endregion
#region 核心字段
private Bitmap _currentImage;
private float _scale = 1.0f;
private PointF _offset = PointF.Empty;
private Point _dragStart;
private bool _isDragging;
private readonly object _imageLock = new object();
#endregion
#region 公开属性
public Bitmap Image
{
get
{
lock (_imageLock)
{
return _currentImage?.Clone() as Bitmap;
}
}
set
{
Bitmap newImage = value?.Clone() as Bitmap;
Bitmap oldImageToDispose = null;
lock (_imageLock)
{
// 交换图像引用
oldImageToDispose = _currentImage;
_currentImage = newImage;
if (_currentImage != null)
{
if (oldImageToDispose?.Size != _currentImage.Size)
{
AutoFit();
}
else
{
ClampOffset();
}
}
}
// 在锁外安全释放旧图像
if (oldImageToDispose != null)
{
// 使用BeginInvoke确保在UI线程释放资源
BeginInvoke(new Action(() =>
{
oldImageToDispose.Dispose();
oldImageToDispose = null;
}));
}
SafeInvalidate();
}
}
#endregion
public ImageViewerControl()
{
InitializeComponents();
SetupDoubleBuffering();
}
#region 初始化
private void InitializeComponents()
{
pictureBox = new PictureBox
{
Dock = DockStyle.Fill,
BackColor = Color.DarkGray,
Cursor = Cursors.Hand
};
statusLabel = new Label
{
Dock = DockStyle.Bottom,
Height = 20,
Text = "就绪",
BorderStyle = BorderStyle.FixedSingle,
Font = new Font("Consolas", 10)
};
pictureBox.MouseDown += PictureBox_MouseDown;
pictureBox.MouseMove += PictureBox_MouseMove;
pictureBox.MouseUp += PictureBox_MouseUp;
pictureBox.MouseWheel += PictureBox_MouseWheel;
pictureBox.Paint += PictureBox_Paint;
Controls.Add(pictureBox);
Controls.Add(statusLabel);
}
private void SetupDoubleBuffering()
{
typeof(PictureBox).GetMethod("SetStyle",
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance)?
.Invoke(pictureBox, new object[] {
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint,
true
});
}
#endregion
#region 核心功能
private void AutoFit()
{
lock (_imageLock)
{
if (_currentImage == null) return;
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
);
_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)
{
Bitmap drawImage = null;
RectangleF destRect;
float scale;
PointF offset;
// 创建临时绘图参数
lock (_imageLock)
{
if (_currentImage == null) return;
// 创建绘图副本
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 鼠标交互(左键拖动)
private void PictureBox_MouseDown(object sender, MouseEventArgs e)
{
if (InvokeRequired) return;
lock (_imageLock)
{
if (_currentImage == null) return;
if (e.Button == MouseButtons.Left)
{
_dragStart = e.Location;
_isDragging = true;
}
}
}
private void PictureBox_MouseMove(object sender, MouseEventArgs e)
{
if (InvokeRequired) return;
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)
{
lock (_imageLock)
{
_isDragging = false;
}
}
private void PictureBox_MouseWheel(object sender, MouseEventArgs e)
{
if (InvokeRequired) return;
lock (_imageLock)
{
if (_currentImage == null) 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);
_offset.X = e.Location.X - imgPos.X * newScale;
_offset.Y = e.Location.Y - imgPos.Y * newScale;
_scale = newScale;
ClampOffset();
SafeInvalidate();
}
}
#endregion
#region 辅助方法
private PointF ConvertToImageCoords(Point mousePos)
{
lock (_imageLock)
{
if (_currentImage == null) return PointF.Empty;
return new PointF(
(mousePos.X - _offset.X) / _scale,
(mousePos.Y - _offset.Y) / _scale);
}
}
private void ClampOffset()
{
lock (_imageLock)
{
if (_currentImage == null) return;
float imgWidth = _currentImage.Width * _scale;
float imgHeight = _currentImage.Height * _scale;
_offset.X = Math.Clamp(_offset.X,
imgWidth > Width ? Width - imgWidth : 0,
imgWidth > Width ? 0 : Width - imgWidth);
_offset.Y = Math.Clamp(_offset.Y,
imgHeight > Height ? Height - imgHeight : 0,
imgHeight > Height ? 0 : Height - imgHeight);
}
}
private void UpdateStatus(Point mousePos)
{
if (InvokeRequired)
{
BeginInvoke(new Action<Point>(UpdateStatus), mousePos);
return;
}
lock (_imageLock)
{
if (_currentImage == null)
{
statusLabel.Text = "无有效图像";
return;
}
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:F1}, {imgPos.Y:F1}) | 缩放: {_scale * 100:0}%" :
$"图像尺寸: {_currentImage.Width}x{_currentImage.Height} | 缩放: {_scale * 100:0}%";
}
}
private void SafeInvalidate()
{
if (InvokeRequired)
{
BeginInvoke(new Action(pictureBox.Invalidate));
}
else
{
pictureBox.Invalidate();
}
}
#endregion
public Bitmap GetCurrentSnapshot()
{
lock (_imageLock)
{
// 返回深拷贝防止原始图像被修改
return _currentImage?.Clone() as Bitmap;
}
}
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
lock (_imageLock)
{
_currentImage?.Dispose();
_currentImage = null;
}
base.Dispose(disposing);
}
}
}