DHDHSoftware/DHSoftware/Views/ImageViewerControl.cs
2025-04-16 08:52:53 +08:00

306 lines
9.7 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 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
}
}