306 lines
9.7 KiB
C#
306 lines
9.7 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 RectangleF _roiRect;
|
||
private PointF _roiStart;
|
||
private bool _isDrawing;
|
||
private Point _dragStart;
|
||
private bool _isDragging;
|
||
private Pen _roiPen = new Pen(Color.Red, 2);
|
||
#endregion
|
||
|
||
#region 公开属性
|
||
#region 公开属性
|
||
public Bitmap Image
|
||
{
|
||
get => _currentImage;
|
||
set
|
||
{
|
||
// 记录旧状态
|
||
var oldSize = _currentImage?.Size ?? Size.Empty;
|
||
var oldScale = _scale;
|
||
var oldOffset = _offset;
|
||
|
||
_currentImage?.Dispose();
|
||
_currentImage = value;
|
||
|
||
if (_currentImage != null)
|
||
{
|
||
if (_currentImage.Size != oldSize)
|
||
{
|
||
// 尺寸不同时:重置ROI、自动适配
|
||
_roiRect = RectangleF.Empty;
|
||
AutoFit();
|
||
}
|
||
else
|
||
{
|
||
// 尺寸相同时:保留缩放和偏移
|
||
_scale = oldScale;
|
||
_offset = oldOffset;
|
||
ClampOffset();
|
||
}
|
||
}
|
||
|
||
pictureBox.Invalidate();
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
public RectangleF CurrentROI => _roiRect;
|
||
#endregion
|
||
|
||
public ImageViewerControl()
|
||
{
|
||
InitializeComponents();
|
||
SetupDoubleBuffering();
|
||
}
|
||
|
||
#region 初始化
|
||
private void InitializeComponents()
|
||
{
|
||
// 主显示区域
|
||
pictureBox = new PictureBox
|
||
{
|
||
Dock = DockStyle.Fill,
|
||
BackColor = Color.DarkGray,
|
||
Cursor = Cursors.Cross
|
||
};
|
||
|
||
// 状态栏
|
||
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()
|
||
{
|
||
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;
|
||
}
|
||
|
||
private void PictureBox_Paint(object sender, PaintEventArgs e)
|
||
{
|
||
if (_currentImage == null) return;
|
||
|
||
// 绘制图像
|
||
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)
|
||
{
|
||
var displayRect = new RectangleF(
|
||
_roiRect.X * _scale + _offset.X,
|
||
_roiRect.Y * _scale + _offset.Y,
|
||
_roiRect.Width * _scale,
|
||
_roiRect.Height * _scale);
|
||
|
||
using (var pen = new Pen(_roiPen.Color, _roiPen.Width / _scale))
|
||
{
|
||
e.Graphics.DrawRectangle(pen,
|
||
displayRect.X,
|
||
displayRect.Y,
|
||
displayRect.Width,
|
||
displayRect.Height);
|
||
}
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region 鼠标事件
|
||
private void PictureBox_MouseDown(object sender, MouseEventArgs e)
|
||
{
|
||
if (e.Button == MouseButtons.Left && _currentImage != null)
|
||
{
|
||
_roiStart = ClampCoordinates(ConvertToImageCoords(e.Location));
|
||
_isDrawing = true;
|
||
}
|
||
else if (e.Button == MouseButtons.Right)
|
||
{
|
||
_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();
|
||
}
|
||
|
||
UpdateStatus(e.Location);
|
||
}
|
||
|
||
private void PictureBox_MouseUp(object sender, MouseEventArgs e)
|
||
{
|
||
_isDragging = false;
|
||
_isDrawing = false;
|
||
}
|
||
|
||
private void PictureBox_MouseWheel(object sender, MouseEventArgs e)
|
||
{
|
||
if (_currentImage == null) return;
|
||
|
||
PointF mousePos = e.Location;
|
||
PointF imgPosBefore = ConvertToImageCoords(mousePos);
|
||
|
||
if (imgPosBefore.X < 0 || imgPosBefore.X > _currentImage.Width ||
|
||
imgPosBefore.Y < 0 || imgPosBefore.Y > _currentImage.Height) return;
|
||
|
||
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;
|
||
|
||
_scale = newScale;
|
||
ClampOffset();
|
||
pictureBox.Invalidate();
|
||
}
|
||
#endregion
|
||
|
||
#region 辅助方法
|
||
private PointF ConvertToImageCoords(PointF 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)));
|
||
}
|
||
|
||
private void ClampOffset()
|
||
{
|
||
if (_currentImage == null) return;
|
||
|
||
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);
|
||
}
|
||
|
||
if (imgHeight <= Height)
|
||
{
|
||
_offset.Y = Math.Clamp(_offset.Y, 0, Height - imgHeight);
|
||
}
|
||
else
|
||
{
|
||
_offset.Y = Math.Clamp(_offset.Y, Height - imgHeight, 0);
|
||
}
|
||
}
|
||
|
||
private void UpdateStatus(Point mousePos)
|
||
{
|
||
if (_currentImage == null) return;
|
||
|
||
PointF imgPos = ConvertToImageCoords(mousePos);
|
||
bool inImage = imgPos.X >= 0 && imgPos.X <= _currentImage.Width &&
|
||
imgPos.Y >= 0 && imgPos.Y <= _currentImage.Height;
|
||
|
||
string roiInfo = _roiRect.IsEmpty ?
|
||
"未选择区域" :
|
||
$"选区: X={_roiRect.X:0} Y={_roiRect.Y:0} {_roiRect.Width:0}x{_roiRect.Height:0}";
|
||
|
||
statusLabel.Text = inImage ?
|
||
$"坐标: ({imgPos.X:0}, {imgPos.Y:0}) | 缩放: {_scale * 100:0}% | {roiInfo}" :
|
||
$"图像尺寸: {_currentImage.Width}x{_currentImage.Height} | 缩放: {_scale * 100:0}% | {roiInfo}";
|
||
}
|
||
|
||
public void ClearROI()
|
||
{
|
||
_roiRect = RectangleF.Empty;
|
||
pictureBox.Invalidate(); // 触发重绘
|
||
UpdateStatus(Point.Empty); // 更新状态栏
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
} |