Home > Enterprise >  writing directly to form's byte array with pointer
writing directly to form's byte array with pointer

Time:09-28

In a winforms application is there any way to draw directly to the pixel buffer/byte array for the window?

I have a bytearray with an image in the format byte[] myimg = new byte[width x height x 4] for an ARGB bitmap, now i want to display it in the form, the only way i know of is first to make a bitmap, then use lockbits to write my pixels into the bitmap, then i set a picturebox.image to my bitmap instance. But i want to skip this step and write directly to the form, if possible without even a picturebox, is this possible?

Update

To clarify, i specifically want to avoid the overhead and slowness of windows handling scaling/stretching. I just want the most efficient way of getting my own pixels onto the form's canvas area, my own byte array being prepared and scaled accordingly in advance by myself.

Update 2

Turns out the lower windows api's does provide a way using bitblt , as jtxkopt has shown, this killed my overhead almost completely

CodePudding user response:

You cannot draw a raw image data directly onto the Form. There is no function like DrawImageData(byte[] data, int width, int height, PixelFormat format);. There are two option I am aware of. You can try one of them that suits to your needs most.

Option 1

You can create a Bitmap class from an image data using the constructor, fill the data as you want and draw the bitmap onto the screen. However, as others stated, doing so you don't gain too much performance increase as long as you stay in managed code.

For a simple implementation, you can see the example code.

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace CustomRendering
{
    public unsafe partial class MainForm : Form
    {
        public MainForm()
        {
            SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, true);
            InitializeComponent();
        }

        private Bitmap m_surfaceBitmap;
        private byte* m_surfaceData;
        private int m_stride;
        private int m_width, m_height;
        protected override void OnPaint(PaintEventArgs e)
        {
            Graphics g = e.Graphics; 
            g.SmoothingMode = SmoothingMode.HighSpeed;
            g.InterpolationMode = InterpolationMode.NearestNeighbor;
            g.PixelOffsetMode = PixelOffsetMode.Half;
            g.DrawImage(m_surfaceBitmap, Point.Empty);
        }
        protected override void OnHandleCreated(EventArgs e)
        {
            this.FormBorderStyle = FormBorderStyle.FixedSingle;
            m_width = ClientSize.Width;
            m_height = ClientSize.Height;
            m_stride = (32 * m_width   31) / 32 * 4; // Calculate the stride.
            m_surfaceData = (byte*)Marshal.AllocHGlobal(m_stride * m_height);
            m_surfaceBitmap = new Bitmap(m_width, m_height, m_stride, PixelFormat.Format32bppArgb, (IntPtr)m_surfaceData);
        }

        protected unsafe override void onm ouseMove(MouseEventArgs e)
        {
            Clear(Color.White);
            FillRectangle(e.X, e.Y, 100, 100, Color.Black);
            Invalidate();
            base.OnMouseMove(e);
        }
        private void Clear(Color color)
        {
            int argb = color.ToArgb();
            for (int i = 0; i < m_stride * m_height; i  = 4) 
                *(int*)(m_surfaceData   i) = argb; 
        }
        private void FillRectangle(int x0, int y0, int width, int height, Color color)
        {
            int argb = color.ToArgb();
            for (int y = y0; y < y0   height; y  ) 
                for (int x = x0; x < x0   width; x  ) 
                    SetPixel(x, y, argb); 
        }

        private void SetPixel(int x, int y, int argb)
        {
            if (x >= m_width || x < 0 || y >= m_height || y < 0)
                return;
            m_surfaceData[y * m_stride   4 * x   0] = (byte)((argb >> 0) & 0x000000FF);
            m_surfaceData[y * m_stride   4 * x   1] = (byte)((argb >> 8) & 0x000000FF);
            m_surfaceData[y * m_stride   4 * x   2] = (byte)((argb >> 16) & 0x000000FF);
            m_surfaceData[y * m_stride   4 * x   3] = (byte)((argb >> 24) & 0x000000FF);
        }
    }

}
Option 2

This is a bit lower-level solution that use Win32 API but I think this one is faster than previous one since it handles the WM_PAINT message itself and uses the bitblt function to display a DIB(Device Independent Bitmap) instead of drawing a Gdiplus Bitmap. I don't explain how a DIB can be created and other Win32 related code works. The example code is below. Compare them and choose one of them.

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace CustomRendering
{ 
    public unsafe partial class MainForm : Form
    {
        public MainForm()
        {
             InitializeComponent();
        }
         
        private byte* m_surfaceData;
        private int m_stride;
        private int m_width, m_height;
        private IntPtr m_hdcWindow;
        private IntPtr m_surfaceDC;
        private IntPtr m_surfaceBitmap;
        private IntPtr m_oldObject;
        private Point m_mouseLocation;
         
        protected override void WndProc(ref Message m)
        {
            // Process WM_PAINT ourself.
            if(m.Msg == 0x000F)
            {
                Clear(Color.White);
                FillRectangle(m_mouseLocation.X, m_mouseLocation.Y, 100, 100, Color.Black);
                PAINTSTRUCT ps;
                IntPtr hdc = BeginPaint(Handle, out ps);
                
                BitBlt(m_hdcWindow, 0, 0, m_width, m_height, m_surfaceDC, 0, 0, RasterOperations.SRCCOPY);
                EndPaint(Handle, ref ps);base.WndProc(ref m);
            }
            // Process WM_ERASEBKGND to prevent flickering.
            else if(m.Msg == 0x0014) 
                m.Result = (IntPtr)1; 
            else
                base.WndProc(ref m);
        }
        protected override void OnHandleCreated(EventArgs e)
        { 
            m_width = Screen.PrimaryScreen.WorkingArea.Width;
            m_height = Screen.PrimaryScreen.WorkingArea.Height;
            m_stride = (32 * m_width   31) / 32 * 4; // Calculate the stride.
            CreateSurface(m_width, m_height);
         }

        protected unsafe override void onm ouseMove(MouseEventArgs e)
        {
            m_mouseLocation = e.Location;
            Invalidate(ClientRectangle); // Invalidate the only visible area.
        }
        private void CreateSurface(int width, int height)
        {
            BITMAPINFO bi = new BITMAPINFO();

            m_hdcWindow = GetDC(Handle);
            m_surfaceDC = CreateCompatibleDC(m_hdcWindow);

            bi.bmiHeader.biSize = (uint)Marshal.SizeOf<BITMAPINFOHEADER>();
            bi.bmiHeader.biWidth = width;
            bi.bmiHeader.biHeight = -height;
            bi.bmiHeader.biPlanes = 1;
            bi.bmiHeader.biBitCount = 32;
            bi.bmiHeader.biCompression = BitmapCompressionMode.BI_RGB; // No compression
            bi.bmiHeader.biSizeImage = (uint)(width * height * 4);
            bi.bmiHeader.biXPelsPerMeter = 0;
            bi.bmiHeader.biYPelsPerMeter = 0;
            bi.bmiHeader.biClrUsed = 0;
            bi.bmiHeader.biClrImportant = 0; 

            IntPtr ppvBits;
            m_surfaceBitmap = CreateDIBSection(m_surfaceDC, ref bi, DIB_RGB_COLORS, out ppvBits, IntPtr.Zero, 0);

            m_surfaceData = (byte*)ppvBits.ToPointer();

            m_oldObject = SelectObject(m_surfaceDC, m_surfaceBitmap);
        }
        private void Clear(Color color)
        {
            int argb = color.ToArgb();
            for (int i = 0; i < m_stride * m_height; i  = 4)
                *(int*)(m_surfaceData   i) = argb;
        }
        private void FillRectangle(int x0, int y0, int width, int height, Color color)
        {
            int argb = color.ToArgb();
            for (int y = y0; y < y0   height; y  )
                for (int x = x0; x < x0   width; x  )
                    SetPixel(x, y, argb);
        }

        private void SetPixel(int x, int y, int argb)
        { 
            m_surfaceData[y * m_stride   4 * x   0] = (byte)((argb >> 0) & 0x000000FF);
            m_surfaceData[y * m_stride   4 * x   1] = (byte)((argb >> 8) & 0x000000FF);
            m_surfaceData[y * m_stride   4 * x   2] = (byte)((argb >> 16) & 0x000000FF);
            m_surfaceData[y * m_stride   4 * x   3] = (byte)((argb >> 24) & 0x000000FF);
        }
        private enum RasterOperations : uint
        {
            SRCCOPY = 0x00CC0020,
            SRCPAINT = 0x00EE0086,
            SRCAND = 0x008800C6,
            SRCINVERT = 0x00660046,
            SRCERASE = 0x00440328,
            NOTSRCCOPY = 0x00330008,
            NOTSRCERASE = 0x001100A6,
            MERGECOPY = 0x00C000CA,
            MERGEPAINT = 0x00BB0226,
            PATCOPY = 0x00F00021,
            PATPAINT = 0x00FB0A09,
            PATINVERT = 0x005A0049,
            DSTINVERT = 0x00550009,
            BLACKNESS = 0x00000042,
            WHITENESS = 0x00FF0062,
            CAPTUREBLT = 0x40000000
        }
        private enum BitmapCompressionMode : uint
        {
            BI_RGB = 0,
            BI_RLE8 = 1,
            BI_RLE4 = 2,
            BI_BITFIELDS = 3,
            BI_JPEG = 4,
            BI_PNG = 5
        }
        [StructLayout(LayoutKind.Sequential)]
        private struct BITMAPINFOHEADER
        {
            public uint biSize;
            public int biWidth;
            public int biHeight;
            public ushort biPlanes;
            public ushort biBitCount;
            public BitmapCompressionMode biCompression;
            public uint biSizeImage;
            public int biXPelsPerMeter;
            public int biYPelsPerMeter;
            public uint biClrUsed;
            public uint biClrImportant; 
        }
        [StructLayoutAttribute(LayoutKind.Sequential)]
        private struct BITMAPINFO
        { 
            public BITMAPINFOHEADER bmiHeader; 
            [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 1, ArraySubType = UnmanagedType.Struct)]
            public int[] bmiColors;
        }
        [StructLayout(LayoutKind.Sequential)]
        private struct RECT
        {
            public int Left, Top, Right, Bottom; 
        }
        [StructLayout(LayoutKind.Sequential)]
        private struct PAINTSTRUCT
        {
            public IntPtr hdc;
            public bool fErase;
            public RECT rcPaint;
            public bool fRestore;
            public bool fIncUpdate;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] rgbReserved;
        }
        private const int DIB_RGB_COLORS = 0;
        private const int DIB_PAL_COLORS = 1;

        [DllImport("user32.dll")]
        private static extern IntPtr GetDC(IntPtr hwnd);

        [DllImport("gdi32.dll")]
        private static extern IntPtr CreateCompatibleDC(IntPtr hdc);
        [DllImport("gdi32.dll")]
        private static extern IntPtr CreateDIBSection(IntPtr hdc, ref BITMAPINFO pbmi, uint usage, out IntPtr ppvBits, IntPtr hSection, uint dwOffset);

        [DllImport("gdi32.dll")]
        private static extern bool BitBlt(IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, RasterOperations dwRop);

        [DllImport("gdi32.dll")]
        private static extern IntPtr SelectObject(IntPtr hdc, IntPtr hNewObj);

        [DllImport("user32.dll")]
        static extern IntPtr BeginPaint(IntPtr hwnd, out PAINTSTRUCT lpPaint);
         
        [DllImport("user32.dll")]
        static extern bool EndPaint(IntPtr hWnd, [In] ref PAINTSTRUCT lpPaint);
    }

}
  • Related