WinForms应用中能否直接写入窗体的像素缓冲区/字节数组?
WinForms中直接写入窗口像素缓冲区的方法
问题描述
我有一个格式为byte[] myimg = new byte[width x height x 4]的ARGB位图字节数组,目前的显示流程是先创建Bitmap,通过LockBits写入像素,再设置PictureBox.Image展示。但想跳过这些步骤,直接写入窗体(甚至不用PictureBox),同时要避免Windows自动缩放/拉伸带来的开销和延迟,只需要高效绘制预先处理好的像素。
解决方案:使用Windows API的BitBlt实现高效绘制
可以通过调用底层Windows GDI API直接操作窗体的设备上下文(DC),结合CreateDIBSection创建与内存字节数组关联的DIB位图,再用BitBlt快速将内存位图复制到窗体DC,完全跳过中间Bitmap对象的创建和PictureBox的使用,消除额外开销。
实现步骤与代码示例
- 导入必要的Windows API函数并实现绘制逻辑:
using System; using System.Drawing; using System.Runtime.InteropServices; using System.Windows.Forms; public partial class MainForm : Form { // Windows API声明 [DllImport("gdi32.dll")] private static extern IntPtr CreateDIBSection(IntPtr hdc, [In] ref BITMAPINFO pbmi, uint iUsage, out IntPtr ppvBits, IntPtr hSection, uint dwOffset); [DllImport("gdi32.dll")] private static extern bool BitBlt(IntPtr hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, uint dwRop); [DllImport("gdi32.dll")] private static extern IntPtr CreateCompatibleDC(IntPtr hdc); [DllImport("gdi32.dll")] private static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj); [DllImport("gdi32.dll")] private static extern bool DeleteDC(IntPtr hdc); [DllImport("gdi32.dll")] private static extern bool DeleteObject(IntPtr hObject); // 位图信息结构体 [StructLayout(LayoutKind.Sequential)] private struct BITMAPINFOHEADER { public uint biSize; public int biWidth; public int biHeight; public ushort biPlanes; public ushort biBitCount; public uint biCompression; public uint biSizeImage; public int biXPelsPerMeter; public int biYPelsPerMeter; public uint biClrUsed; public uint biClrImportant; } [StructLayout(LayoutKind.Sequential)] private struct BITMAPINFO { public BITMAPINFOHEADER bmiHeader; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] public uint[] bmiColors; } private const uint DIB_RGB_COLORS = 0x00000000; private const uint SRCCOPY = 0x00CC0020; // 你的ARGB字节数组 private byte[] myimg; private int width; private int height; public MainForm() { InitializeComponent(); // 初始化示例数据(实际替换为你的像素数组) width = 640; height = 480; myimg = new byte[width * height * 4]; // 填充测试像素(比如红色) for (int i = 0; i < myimg.Length; i += 4) { myimg[i] = 255; // B myimg[i + 1] = 0; // G myimg[i + 2] = 0; // R myimg[i + 3] = 255; // A } } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); DrawDirectlyToForm(e.Graphics); } private void DrawDirectlyToForm(Graphics g) { IntPtr hdc = g.GetHdc(); IntPtr memDC = CreateCompatibleDC(hdc); try { BITMAPINFO bmi = new BITMAPINFO(); bmi.bmiHeader.biSize = (uint)Marshal.SizeOf(typeof(BITMAPINFOHEADER)); bmi.bmiHeader.biWidth = width; bmi.bmiHeader.biHeight = -height; // 负数值表示顶部为原点(符合GDI+坐标系) bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; // ARGB32 bmi.bmiHeader.biCompression = 0; // BI_RGB bmi.bmiHeader.biSizeImage = (uint)(width * height * 4); // 创建DIBSection,直接关联我们的字节数组 IntPtr pBits; IntPtr hBitmap = CreateDIBSection(hdc, ref bmi, DIB_RGB_COLORS, out pBits, IntPtr.Zero, 0); if (hBitmap != IntPtr.Zero) { // 将字节数组复制到DIB的内存区域 Marshal.Copy(myimg, 0, pBits, myimg.Length); // 将DIB选入内存DC IntPtr oldBitmap = SelectObject(memDC, hBitmap); // BitBlt到窗体DC BitBlt(hdc, 0, 0, width, height, memDC, 0, 0, SRCCOPY); // 恢复原来的位图并清理 SelectObject(memDC, oldBitmap); DeleteObject(hBitmap); } } finally { DeleteDC(memDC); g.ReleaseHdc(hdc); } } // 如果需要实时更新,调用Invalidate()触发重绘 public void UpdatePixels(byte[] newPixels) { myimg = newPixels; Invalidate(); } }
- 关键说明:
CreateDIBSection创建的DIB位图直接与你的字节数组内存关联,无需额外的LockBits操作,减少内存拷贝。BitBlt是硬件加速的位图复制操作,性能远高于GDI+的常规绘制方法,完全避免Windows自动缩放(因为你已经预先处理好像素尺寸)。- 重写窗体的
OnPaint方法,在每次重绘时直接调用DrawDirectlyToForm,确保像素正确显示。
内容的提问来源于stack exchange,提问作者Lasse
相关产品推荐
相关产品推荐

