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