前言

  • 本文介绍使用 C# 实现自定义图像控件,该控件提供了图像显示、缩放、平移、像素级查看的视觉辅助功能。
  • 控件启用了双缓冲优化绘制性能,支持高精度图像的详细信息展示。
  • 该控件是在之前的WinForm图像控件的基础上增加了一些功能。添加了像素格显示、放大RGB值填充像素的炫酷效果。
  • 使用可视化方式可以直观地显示图像中每个像素的RGB分量值,同时保持颜色分离显示,便于分析像素颜色组成。

运行环境

  • 操作系统: Windows 11
    开发环境: Visual Studio 2022
    框架版本: .NET Framework 4.8

运行效果

像素格效果

RGB值填充像素效果

RGB值填充像素效果2

C#自定义控件图像放大后的RGB值填充像素效果_c#


实现原理

  • 将图像分割为像素网格。
  • 每个像素格分为三个垂直部分,分别用纯红、纯绿、纯蓝填充。
  • 在每个颜色区域显示实际像素的对应通道值(R、G、B)。
  • 每个像素格有黑色边框。
  • 绘制RGB值文本。

功能代码:

private void DrawPixelGridWithColorValues(PaintEventArgs e, int startX, int startY, int endX, int endY, RectangleF imageRect)
{// 创建临时Bitmapif (!(_image is Bitmap bitmap)){bitmap = new Bitmap(_image); }// 文本格式设置StringFormat format = new StringFormat{Alignment = StringAlignment.Center,LineAlignment = StringAlignment.Center};// 动态计算字体大小(基于缩放级别)//float fontSize = Math.Max(6, _zoomFactor / 2);float fontSize = 6;Font valueFont = new Font("Arial", fontSize, FontStyle.Bold);// 边距设置(占整个像素格的10%)float marginRatio = 0.1f;float margin = _zoomFactor * marginRatio;// 定义三原色的纯色填充Brush redBrush = new SolidBrush(Color.FromArgb(255, 0, 0));    // 纯红Brush greenBrush = new SolidBrush(Color.FromArgb(0, 255, 0));  // 纯绿Brush blueBrush = new SolidBrush(Color.FromArgb(0, 0, 255));   // 纯蓝Brush textBrush = Brushes.White;                               // 文本颜色for (int y = startY; y < endY; y++){for (int x = startX; x < endX; x++){try{Color pixelColor = bitmap.GetPixel(x, y);float pixelLeft = imageRect.X + x * _zoomFactor;float pixelTop = imageRect.Y + y * _zoomFactor;// 计算带边距的内部矩形RectangleF innerRect = new RectangleF(pixelLeft + margin,pixelTop + margin,_zoomFactor - 2 * margin,_zoomFactor - 2 * margin);// 分割为三个垂直区域(红、绿、蓝)float partHeight = innerRect.Height / 3;// 1. 红色区域(总是纯红)RectangleF redRect = new RectangleF(innerRect.X, innerRect.Y,innerRect.Width, partHeight);e.Graphics.FillRectangle(redBrush, redRect);// 2. 绿色区域(总是纯绿)RectangleF greenRect = new RectangleF(innerRect.X, innerRect.Y + partHeight,innerRect.Width, partHeight);e.Graphics.FillRectangle(greenBrush, greenRect);// 3. 蓝色区域(总是纯蓝)RectangleF blueRect = new RectangleF(innerRect.X, innerRect.Y + 2 * partHeight,innerRect.Width, partHeight);e.Graphics.FillRectangle(blueBrush, blueRect);// 在每个区域显示实际像素值(文本)e.Graphics.DrawString(pixelColor.R.ToString(), valueFont, textBrush, redRect, format);e.Graphics.DrawString(pixelColor.G.ToString(), valueFont, textBrush, greenRect, format);e.Graphics.DrawString(pixelColor.B.ToString(), valueFont, textBrush, blueRect, format);// 绘制外边框e.Graphics.DrawRectangle(Pens.Black, pixelLeft, pixelTop, _zoomFactor, _zoomFactor);}catch { }}}// 释放资源if (bitmap != _image) bitmap.Dispose();valueFont.Dispose();format.Dispose();redBrush.Dispose();greenBrush.Dispose();blueBrush.Dispose();
}

主要功能

  • 图像显示与缩放(支持0.1x-200x缩放)。
  • 鼠标拖拽平移图像。
  • 背景网格显示。
  • 像素级网格显示(高缩放级别时)。
  • 详细的坐标和颜色信息显示。
  • 三原色分解显示(极高缩放级别时)。

完整代码

public partial class UImage : Control
{#region 事件|字段|属性#region 事件public event Action<PointF> MouseImagePositionChanged;      //鼠标图像点变更事件#endregion#region 字段// ==================== 图像显示参数 ====================private Image _image;                               // 当前显示的图像private Point _lastDragPosition;                    // 上次拖拽时的鼠标位置private PointF _imagePosition = new PointF(0, 0);   // 图像在控件中的显示位置private float _zoomFactor = 1.0f;                   // 当前缩放比例private const float MinimumZoom = 0.10f;            // 最小缩放比例private const float MaximumZoom = 200.0f;           // 最大缩放比例private const float ZoomIncrement = 0.3f;           // 缩放步长// ==================== 背景网格参数 ====================private int _backgroundGridSize = 15;                           // 背景网格大小(像素)private Color _backgroundColor1 = Color.FromArgb(60, 60, 60);   // 背景颜色1private Color _backgroundColor2 = Color.FromArgb(80, 80, 80);   // 背景颜色2private PointF _currentImagePoint = new PointF(-1.0F, -1.0F);   // 当前鼠标指向的图像像素坐标// ==================== 信息显示参数 ====================private Font _infoDisplayFont = new Font("Arial", 12);              // 信息显示字体private Color _infoTextColor = Color.DarkGray;                      // 信息文本颜色private Color _infoBackgroundColor = Color.FromArgb(128, 0, 0, 0);  // 信息背景颜色(半透明黑)// ==================== 缓存对象 ====================private SolidBrush _infoTextBrush;       // 信息文本画笔缓存private SolidBrush _infoBackgroundBrush; // 信息背景画笔缓存private SolidBrush _gridBrush1;          // 网格颜色1画笔缓存private SolidBrush _gridBrush2;          // 网格颜色2画笔缓存// ==================== 像素网格参数 ====================private bool _showPixelGrid = true;                              // 是否显示像素网格private float _pixelGridThreshold = 5.0f;                        // 显示像素网格的缩放阈值private Color _pixelGridColor = Color.FromArgb(20, Color.White); // 像素网格颜色private Pen _pixelGridPen;                                       // 像素网格画笔缓存#endregion#region 属性// ==================== 属性定义 ====================/// <summary>获取或设置控件显示的图像</summary>[Category("UserDefine")][Description("控件显示的图像")]public Image Image{get => _image;set{if (_image == value) return;_image = value;ImageCenter();Invalidate();}}/// <summary>获取当前鼠标指向的图像像素坐标</summary>[Category("UserDefine")][Description("当前像素点")][DefaultValue(typeof(double), "-1.0, -1.0")]public PointF CurrentImagePoint{get => _currentImagePoint;private set{if (_currentImagePoint == value) return;_currentImagePoint = value;Invalidate();}}/// <summary>获取或设置背景网格大小(像素)</summary>[Category("UserDefine")][Description("控件网格大小")][DefaultValue("15")]public int BackgroundGridSize{get => _backgroundGridSize;set{if (_backgroundGridSize == value) return;_backgroundGridSize = value;Invalidate();  // 网格大小改变需要重绘}}/// <summary>获取或设置背景颜色1</summary>[Category("UserDefine")][Description("控件背景色1")][DefaultValue("60, 60, 60")]public Color BackgroundColor1{get => _backgroundColor1;set{if (_backgroundColor1 == value) return;_backgroundColor1 = value;_gridBrush1 = new SolidBrush(value); // 创建新画笔Invalidate();}}/// <summary>获取或设置背景颜色2</summary>[Category("UserDefine")][Description("控件背景色2")][DefaultValue("80, 80, 80")]public Color BackgroundColor2{get => _backgroundColor2;set{if (_backgroundColor2 == value) return;_backgroundColor2 = value;_gridBrush2 = new SolidBrush(value);Invalidate();}}/// <summary>获取或设置信息显示字体</summary>[Category("UserDefine")][Description("信息显示字体")]public Font InfoDisplayFont{get => _infoDisplayFont;set{if (_infoDisplayFont == value) return;_infoDisplayFont = value;     // 设置新字体Invalidate();}}/// <summary>获取或设置信息文本颜色</summary>[Category("UserDefine")][Description("信息显示颜色")][DefaultValue(typeof(Color), "DarkGray")]public Color InfoTextColor{get => _infoTextColor;set{if (_infoTextColor == value) return;_infoTextColor = value;_infoTextBrush = new SolidBrush(value);Invalidate();}}/// <summary>获取或设置信息背景颜色</summary>[Category("UserDefine")][Description("信息背景颜色")][DefaultValue(typeof(Color), "128, 0, 0, 0")]public Color InfoBackgroundColor{get => _infoBackgroundColor;set{if (_infoBackgroundColor == value) return;_infoBackgroundColor = value;_infoBackgroundBrush = new SolidBrush(value);Invalidate();}}/// <summary>获取或设置是否显示像素网格</summary>[Category("UserDefine")][Description("是否显示像素网格")][DefaultValue(true)]public bool ShowPixelGrid{get => _showPixelGrid;set{if (_showPixelGrid == value) return;_showPixelGrid = value;Invalidate();}}/// <summary>获取或设置显示像素网格的缩放阈值</summary>[Category("UserDefine")][Description("显示像素网格的缩放阈值")][DefaultValue(5.0f)]public float PixelGridThreshold{get => _pixelGridThreshold;set{if (_pixelGridThreshold == value) return;_pixelGridThreshold = value;Invalidate();}}/// <summary>获取或设置像素网格颜色</summary>[Category("UserDefine")][Description("像素网格颜色")][DefaultValue(typeof(Color), "100, 255, 255, 255")]public Color PixelGridColor{get => _pixelGridColor;set{if (_pixelGridColor == value) return;_pixelGridColor = value;_pixelGridPen = new Pen(value);Invalidate();}}#endregion#endregion#region 构造函数public UImage(){// 启用双缓冲减少闪烁DoubleBuffered = true; this.DoubleBuffered = true;this.SetStyle(ControlStyles.OptimizedDoubleBuffer |ControlStyles.UserPaint |ControlStyles.AllPaintingInWmPaint |ControlStyles.ResizeRedraw, true);this.UpdateStyles();// 初始化画笔缓存_gridBrush1 = new SolidBrush(_backgroundColor1);_gridBrush2 = new SolidBrush(_backgroundColor2);_infoTextBrush = new SolidBrush(_infoTextColor);_infoBackgroundBrush = new SolidBrush(_infoBackgroundColor);_pixelGridPen = new Pen(_pixelGridColor);}#endregion#region 图像处理方法// ==================== 私有方法 ====================/// <summary>将屏幕坐标转换为图像坐标</summary>private PointF ScreenToImageCoordinates(Point screenPoint){return new PointF((screenPoint.X - _imagePosition.X) / _zoomFactor,(screenPoint.Y - _imagePosition.Y) / _zoomFactor);}/// <summary>获取图像在控件中的显示区域</summary>private RectangleF GetImageDisplayRectangle(){if (_image == null) return RectangleF.Empty;return new RectangleF(_imagePosition.X,_imagePosition.Y, _image.Width * _zoomFactor, _image.Height * _zoomFactor);}/// <summary>居中显示图像,自动计算合适的缩放比例</summary>private void ImageCenter(){if (_image == null) return;// 判断图像是否大于控件显示区域bool isImageLarger = _image.Width > Width || _image.Height > Height;// 计算缩放比例:图像大于控件时自适应,否则使用原始尺寸_zoomFactor = isImageLarger ? Math.Min((float)Width / _image.Width, (float)Height / _image.Height) : 1.0f;// 限制缩放比例在有效范围内_zoomFactor = Math.Max(MinimumZoom, Math.Min(MaximumZoom, _zoomFactor));// 计算缩放后的图像显示尺寸float displayWidth = _image.Width * _zoomFactor;float displayHeight = _image.Height * _zoomFactor;// 计算居中位置_imagePosition = new PointF((Width - displayWidth) / 2, (Height - displayHeight) / 2);Invalidate(); }/// <summary>重置图像显示(缩放比例恢复为1.0并居中)</summary>public void ResetImage(){_zoomFactor = 1.0f;  // 重置缩放比例ImageCenter();       // 居中显示}/// <summary>获取指定图像坐标处的像素颜色</summary>private Color GetPixelColorAt(PointF imagePoint){// 检查坐标是否有效if (_image == null ||imagePoint.X < 0 || imagePoint.X >= _image.Width ||imagePoint.Y < 0 || imagePoint.Y >= _image.Height){return Color.Empty;  // 无效坐标返回空颜色}try{// 如果是Bitmap直接获取像素if (_image is Bitmap bitmap){return bitmap.GetPixel((int)imagePoint.X, (int)imagePoint.Y);}// 对于其他Image类型,创建临时Bitmap获取像素using (Bitmap tempBitmap = new Bitmap(_image)){return tempBitmap.GetPixel((int)imagePoint.X, (int)imagePoint.Y);}}catch{return Color.Empty;  // 出错返回空颜色}}/// <summary>绘制背景网格</summary>private void DrawBackgroundGrid(PaintEventArgs e){// 获取需要重绘的区域Rectangle clipRect = e.ClipRectangle;  // 计算网格起始位置(对齐到网格边界)int startX = (int)(clipRect.Left / _backgroundGridSize) * _backgroundGridSize;int startY = (int)(clipRect.Top / _backgroundGridSize) * _backgroundGridSize;// 遍历所有需要绘制的网格单元格for (int y = startY; y < clipRect.Bottom; y += _backgroundGridSize){for (int x = startX; x < clipRect.Right; x += _backgroundGridSize){// 交替使用两种颜色绘制网格bool isAlternateCell = ((x / _backgroundGridSize) + (y / _backgroundGridSize)) % 2 == 0;var brush = isAlternateCell ? _gridBrush1 : _gridBrush2;e.Graphics.FillRectangle(brush, x, y, _backgroundGridSize, _backgroundGridSize);}}}/// <summary>绘制像素网格</summary>private void DrawPixelGrid(PaintEventArgs e){if (_image == null || !_showPixelGrid || _zoomFactor < _pixelGridThreshold) return;RectangleF imageRect = GetImageDisplayRectangle();// 计算可见的图像区域int startX = Math.Max(0, (int)((-imageRect.X) / (_zoomFactor * _image.Width) * _image.Width));int startY = Math.Max(0, (int)((-imageRect.Y) / (_zoomFactor * _image.Height) * _image.Height));int endX = Math.Min(_image.Width, (int)((Width - imageRect.X) / (_zoomFactor * _image.Width) * _image.Width) + 1);int endY = Math.Min(_image.Height, (int)((Height - imageRect.Y) / (_zoomFactor * _image.Height) * _image.Height) + 1);// 如果是最大缩放级别,绘制三原色填充和文本 // 0.1f 考虑浮点精度误差if (_zoomFactor >= MaximumZoom - 5 - 0.1f || _zoomFactor > 90) {DrawPixelGridWithColorValues(e, startX, startY, endX, endY, imageRect);}// 原始像素网格绘制逻辑else{// 绘制垂直线for (int x = startX; x <= endX; x++){float screenX = imageRect.X + x * _zoomFactor;e.Graphics.DrawLine(_pixelGridPen, screenX, imageRect.Top, screenX, imageRect.Bottom);}// 绘制水平线for (int y = startY; y <= endY; y++){float screenY = imageRect.Y + y * _zoomFactor;e.Graphics.DrawLine(_pixelGridPen, imageRect.Left, screenY, imageRect.Right, screenY);}}}/// <summary>绘制带三原色填充和像素值的像素网格(纯色填充,显示实际值)</summary>private void DrawPixelGridWithColorValues(PaintEventArgs e, int startX, int startY, int endX, int endY, RectangleF imageRect){// 创建临时Bitmapif (!(_image is Bitmap bitmap)){bitmap = new Bitmap(_image); }// 文本格式设置StringFormat format = new StringFormat{Alignment = StringAlignment.Center,LineAlignment = StringAlignment.Center};// 动态计算字体大小(基于缩放级别)//float fontSize = Math.Max(6, _zoomFactor / 2);float fontSize = 6;Font valueFont = new Font("Arial", fontSize, FontStyle.Bold);// 边距设置(占整个像素格的10%)float marginRatio = 0.1f;float margin = _zoomFactor * marginRatio;// 定义三原色的纯色填充Brush redBrush = new SolidBrush(Color.FromArgb(255, 0, 0));    // 纯红Brush greenBrush = new SolidBrush(Color.FromArgb(0, 255, 0));  // 纯绿Brush blueBrush = new SolidBrush(Color.FromArgb(0, 0, 255));   // 纯蓝Brush textBrush = Brushes.White;                               // 文本颜色for (int y = startY; y < endY; y++){for (int x = startX; x < endX; x++){try{Color pixelColor = bitmap.GetPixel(x, y);float pixelLeft = imageRect.X + x * _zoomFactor;float pixelTop = imageRect.Y + y * _zoomFactor;// 计算带边距的内部矩形RectangleF innerRect = new RectangleF(pixelLeft + margin,pixelTop + margin,_zoomFactor - 2 * margin,_zoomFactor - 2 * margin);// 分割为三个垂直区域(红、绿、蓝)float partHeight = innerRect.Height / 3;// 1. 红色区域(总是纯红)RectangleF redRect = new RectangleF(innerRect.X, innerRect.Y,innerRect.Width, partHeight);e.Graphics.FillRectangle(redBrush, redRect);// 2. 绿色区域(总是纯绿)RectangleF greenRect = new RectangleF(innerRect.X, innerRect.Y + partHeight,innerRect.Width, partHeight);e.Graphics.FillRectangle(greenBrush, greenRect);// 3. 蓝色区域(总是纯蓝)RectangleF blueRect = new RectangleF(innerRect.X, innerRect.Y + 2 * partHeight,innerRect.Width, partHeight);e.Graphics.FillRectangle(blueBrush, blueRect);// 在每个区域显示实际像素值(文本)e.Graphics.DrawString(pixelColor.R.ToString(), valueFont, textBrush, redRect, format);e.Graphics.DrawString(pixelColor.G.ToString(), valueFont, textBrush, greenRect, format);e.Graphics.DrawString(pixelColor.B.ToString(), valueFont, textBrush, blueRect, format);// 绘制外边框e.Graphics.DrawRectangle(Pens.Black, pixelLeft, pixelTop, _zoomFactor, _zoomFactor);}catch { }}}// 释放资源if (bitmap != _image) bitmap.Dispose();valueFont.Dispose();format.Dispose();redBrush.Dispose();greenBrush.Dispose();blueBrush.Dispose();}#endregion#region 重写事件方法/// <summary>绘制控件</summary>protected override void OnPaint(PaintEventArgs e){base.OnPaint(e);// 设置高质量图像绘制参数e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;// 绘制背景网格DrawBackgroundGrid(e);if (_image == null)return;// 绘制图像RectangleF imageRect = GetImageDisplayRectangle();e.Graphics.DrawImage(_image, imageRect);DrawPixelGrid(e);// ===== 准备显示信息 =====// 1、图像尺寸信息string imageDimensions = $"W*H    {_image.Width:D4}*{_image.Height:D4}  ";// 2、坐标信息string xCoord = _currentImagePoint.X >= 0 ? _currentImagePoint.X.ToString("0000.0") : "N/A  ";string yCoord = _currentImagePoint.Y >= 0 ? _currentImagePoint.Y.ToString("0000.0") : "N/A  ";string coordinateInfo = $"  X: {xCoord}, Y: {yCoord}  ";// 3、颜色信息Color pixelColor = GetPixelColorAt(_currentImagePoint);string rValue = pixelColor != Color.Empty ? pixelColor.R.ToString("D3") : "N/A";string gValue = pixelColor != Color.Empty ? pixelColor.G.ToString("D3") : "N/A";string bValue = pixelColor != Color.Empty ? pixelColor.B.ToString("D3") : "N/A";string colorInfo = $"  R: {rValue}  G: {gValue}  B: {bValue}";// 4、缩放比例 string imageScale = $" {_zoomFactor.ToString("F3")} ";// 组合显示文本string displayText = $"{imageDimensions}|{coordinateInfo}|{colorInfo} |{imageScale}";// 计算信息显示区域大小SizeF maxTextSize = e.Graphics.MeasureString(displayText, _infoDisplayFont);// 计算文本位置(右下角,留出边距)float textX = ClientSize.Width - maxTextSize.Width - 10;float textY = ClientSize.Height - maxTextSize.Height - 10;// 确保文本不会超出控件边界textX = Math.Max(10, textX);textY = Math.Max(10, textY);// 绘制文本背景(半透明矩形)e.Graphics.FillRectangle(_infoBackgroundBrush, textX, textY, Width, 30);// 绘制文本e.Graphics.DrawString(displayText, _infoDisplayFont, _infoTextBrush, textX, textY);}/// <summary>控件大小改变时调用</summary>protected override void OnResize(EventArgs e){base.OnResize(e);ImageCenter();   // 保持图像居中}/// <summary>双击控件时调用</summary>protected override void OnDoubleClick(EventArgs e){base.OnDoubleClick(e);ResetImage();  // 双击重置图像显示}/// <summary>鼠标按下时调用</summary>protected override void OnMouseDown(MouseEventArgs e){base.OnMouseDown(e);if (e.Button == MouseButtons.Left && _image != null){_lastDragPosition = e.Location;  // 记录拖拽起始位置Cursor = Cursors.Hand;           // 改变鼠标指针形状}}/// <summary>鼠标释放时调用</summary>protected override void OnMouseUp(MouseEventArgs e){base.OnMouseUp(e);Cursor = Cursors.Default;  // 恢复默认鼠标指针}/// <summary>鼠标移动时调用</summary>protected override void OnMouseMove(MouseEventArgs e){base.OnMouseMove(e);if (_image != null){// 更新当前鼠标指向的图像坐标RectangleF imageRect = GetImageDisplayRectangle();CurrentImagePoint = imageRect.Contains(e.Location)? ScreenToImageCoordinates(e.Location): new PointF(-1, -1);MouseImagePositionChanged.Invoke(CurrentImagePoint);}// 处理鼠标拖拽平移图像if (e.Button == MouseButtons.Left && _image != null){_imagePosition.X += (e.X - _lastDragPosition.X);_imagePosition.Y += (e.Y - _lastDragPosition.Y);_lastDragPosition = e.Location;Invalidate();  // 请求重绘}}/// <summary>鼠标滚轮滚动时调用</summary>protected override void OnMouseWheel(MouseEventArgs e){base.OnMouseWheel(e);if (_image == null) return;// 获取缩放前的鼠标位置(图像坐标)PointF mouseImagePosition = ScreenToImageCoordinates(e.Location);// 调整缩放比例(根据滚轮方向)_zoomFactor += (e.Delta > 0 ? (ZoomIncrement* _zoomFactor/2) : (-ZoomIncrement * _zoomFactor/2));// 限制缩放比例在有效范围内_zoomFactor = Math.Max(MinimumZoom, Math.Min(MaximumZoom, _zoomFactor));// 计算缩放后的鼠标位置(屏幕坐标)PointF mouseScreenPositionAfterZoom = new PointF(mouseImagePosition.X * _zoomFactor + _imagePosition.X,mouseImagePosition.Y * _zoomFactor + _imagePosition.Y);// 调整图像位置,使鼠标指向的图像点保持不动_imagePosition.X += (e.Location.X - mouseScreenPositionAfterZoom.X);_imagePosition.Y += (e.Location.Y - mouseScreenPositionAfterZoom.Y);Invalidate();  // 请求重绘}#endregion
}

总结

  • 该自定义控件实现了图像显示、缩放、平移、像素查看等图像常用功能,支持中心点保持的缩放、平滑的拖拽操作,以及图像信息显示和反馈功能,可以说功能较全、具有良好的用户体验、定制性强、性能优化等特点。

最后

  • 项目源码: gitee.com/incodenotes/csharp-control


  • 也可以关注微信公众号 [编程笔记in] ,一起交流学习!