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

346 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
}
}