Learn how to a build a cloud-first strategyRegister Now

x
?
Solved

Display image from a byte array quickly

Posted on 2006-04-26
27
Medium Priority
?
4,665 Views
Last Modified: 2012-05-05
Hello,

I am building an application which recieves real-time data from a high speed camera (up to 35000fps) and can preview that data to the user.  The data is sent as a raw byte array which consists simply of the black and white image data (no headers).  This data is transfered over a TCP connection and is downloaded to the user machine as it is receieved.  

Since the data is transfered at an immense speed, I am looking for the fastest possible way to display the image on screen ("preview") with the smallest processing.  My current method is the following:

public void ConvertPreview(byte[] pixels, Bitmap bmp)
{
             // Copy byte[] into Bitmap to be displayed
             BitmapData d = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
             Marshal.Copy(pixels, 0, d.Scan0, pixels.Length);
             bmp.UnlockBits(d);

            // Display image to user
            this.pictureBox1.Image = bmp;
}

since the image gets processed twice (once to insert it into the bitmap, and then again when it is displayed in the picturebox) I am under the impression there must be a faster way to display the images. In addition, sometimes the methods overlap each other and I get "object in use elsewhere" errors (ie. the picturebox is trying to convert the image to be displayed at the same time that the lockbits is being called on the next image in the queue).

Does anyone know how to do any of the following, and if they will improve performance:
.
(a) skip the picturebox and draw the image directly on the GUI window?
(b) skip inserting the byte[] into the Bitmap object and just display the raw data?
(c) speed up the current process?

The ideal method would simply take the raw data and display it on the screen - no pictureboxes, no Bitmaps.  I'm not sure if this is possible, but this would be the fastest way I can think of.

The fastest conversion method clearly wins in this instance.  Unsafe code is very much accepted.

Thanks!
0
Comment
Question by:TLevin10
  • 11
  • 10
  • 2
  • +2
27 Comments
 
LVL 5

Expert Comment

by:GENTP
ID: 16546497

picLogo.Image = img;


public void ConvertPreview(byte[] pixels)
{

Image img = Image.FromStream(new MemoryStream(pixels,0,pixels.Length));

            // Display image to user
            this.pictureBox1.Image = img;
}

try this
0
 

Author Comment

by:TLevin10
ID: 16546815
unfortunately, this doesn't work since the pixels data doesn't contain any headers, as I mentioned.  

For all intensive purposes, its not an IMAGE thats being transfered but just the pixel data.  The Image.FromStream() method expects that the byte[] is that of an image, not the pixel data itself.
0
 

Author Comment

by:TLevin10
ID: 16546826
Also, this is no different than my previous method (actually slower) since it creates the image from the stream and then processes it again to display to the user.
0
Concerto's Cloud Advisory Services

Want to avoid the missteps to gaining all the benefits of the cloud? Learn more about the different assessment options from our Cloud Advisory team.

 
LVL 5

Expert Comment

by:GENTP
ID: 16547273
Sorry I didnt really read your question all the way through.
0
 
LVL 12

Expert Comment

by:AGBrown
ID: 16549025
For fps rates of 35000, I wonder if you are going to have to use DirectX. THere's a .NET SDK for it. I've dabbled, but not enough to be able to tell you if that is the way to go, but for anything that needs decent graphics performance, I don't think you can use GDI+ which is what the System.Drawing namespaces use.

Andy
0
 

Author Comment

by:TLevin10
ID: 16549673
Hi AG,

Thats an interesting idea.  Didn't think about switching up to DirectX.  I thought DirectX is used more for video than for images - is this true?  I have never even looked at the DirectX SDK.  Where can I find it?
0
 

Author Comment

by:TLevin10
ID: 16549696
Also, on another note, clearly I can't display all 35000 frames to the user.  I am using a sequencing algorithm in order to "pick and choose" enough frames so that it "appears" as video to the user.  Only if the speed is slowed down can the images be viewed - the human eye definitely can't process 35k fps!

My biggest problem arises in doing any sort of "processing" to the images.  Since the conversion algorithm takes ~ 30-50ms, the user is already only viewing about 20-25 frames/sec.  This isn't a bad rate and appears as if it was streaming video, but if I add image processing on top of that (gamma, constrast, whatever) there is additional processing (~100ms/image) and now they are only seeing about 10 frames/sec (few enough that it becomes noticably choppy).  I want to improve the algorithms on both ends so that I can process and display the image with minimal conversion and maximum speed.

Can anyone explain how the image gets displayed on screen, and if DirectX will speed up all parts of the image display process?
0
 
LVL 48

Expert Comment

by:AlexFM
ID: 16550516
You van lock byte[] array in memory using GCHandle.Alloc Method (Object, GCHandleType) with GCHandleType.Pinned parameter and get unmanaged pointer to it using GCHandle.AddrOfPinnedObject Method. Unmanaged array can be displayed directly using StretchDIBits API. This saves copy operation.
Image processing in real time must be done using lookup tables which are prepared before - this allows to replace every pixel without calculations:
pixel[i] = lookupTable[pixel[i]];   // pseudo code
0
 
LVL 11

Expert Comment

by:Agarici
ID: 16550637
i think your code will be at least a couple of times faster if you put your copy code in an unsafe block and do the copy yourself, byte by byte, whtout using mashal.copy function.

hth,
A.
0
 
LVL 48

Expert Comment

by:AlexFM
ID: 16550696
Mashal.copy is actually memcpy. Is copying byte by byte faster?
0
 
LVL 11

Expert Comment

by:Agarici
ID: 16550846
no, it isnt. sorry.

A.
0
 
LVL 12

Expert Comment

by:AGBrown
ID: 16551640
Sounds like Alex is onto something (at least, I haven't got a clue what he's talking about, so it must be pretty good!).

I honestly can't say "how". I was assured by some gfx genius that DirectX would be better for what I was doing in terms of frame rates. Unfortunately that was two months ago, and the .NET 2.0 DX9 SDK wasn't out of beta at that point, so I left that one hanging. However, if your slowest part is the image processing, by which you mean byte-by-byte processing I assume, I can't see that that will be any faster regardless of which particular technology you use.

I was looking at DX9 3D graphics, so I could tell you about that (triangles, dude!), but not about 2D graphics, though I know there is a namespace full of objects for 2D. The advantage (I am lead to believe) that DX9 will give you is management of which bits you do using the CPU and which bits you do using the Gfx card's memory and processor.

A
0
 

Author Comment

by:TLevin10
ID: 16552151
Hi AlexFM,

I think you might be on to something here - I was thinking to myself last night that maybe I could lock the bitmap location in memory (thats all lockBits is doing anyway) and then just find some way to display the image while the bitmap is maintained at that location.  This way, I could have a permanent address for the Scan0 location of the image and avoid the lockbits/unlock all together.  

Could you further explain or possibly give an example of how you would copy the byte[] to unmanaged and display the unmanaged array using the StretchDIBits API?

Thanks -
0
 
LVL 48

Expert Comment

by:AlexFM
ID: 16552414
Though it is possible to pass Scan0 to some unmanaged function because bits are locked, there is no need to do this - bitmap is ready for drawing. However,  when bits are copied to Bitmap, it's too late - copy operation is already done and time is wasted.
I suggest you to get unmanaged pointer to byte[] array directly, without copying. This is done by the following way:

public void ConvertPreview(byte[] pixels)
{
    GCHandle h = GCHandle.Alloc(pixels, GCHandleType.Pinned);

    IntPtr ptr = h.AddrOfPinnedObject(ptr);

    // At this point ptr.ToPointer() is unmanaged void* pointer which points to bytes array - without copying to bitmap.
    // You can pass it to unmanaged API StretchDIBits which draws it to window device context

    h.Free();
}

0
 
LVL 48

Expert Comment

by:AlexFM
ID: 16552470
Search for PInvoke StretchDIBits samples in the WEB - this is not too complicated. You can do this also in C++/CLI library which can call unmanaged functions directly without PInvoke.
BTW, C++/CLI is the best language for such things, including image processing. In C++/CLI this can look by such way:

public void ConvertPreview(array<Byte>^ pixels, IntPtr hDC)
{
    GCHandle h = GCHandle::Alloc(pixels, GCHandleType::Pinned);

    IntPtr ptr = h.AddrOfPinnedObject(ptr);

    StretchBIBits( hDC.ToInt32(), ...   ptr.ToPointer() ... );    // among other parameters

    h.Free();

}
0
 

Author Comment

by:TLevin10
ID: 16556440
Hi AlexFM,

I did some research on StretchDIBits and I decided it was best to learn using a test project.  Unforunately, I can't seem to get the test image to draw on the page.  I have included a my sample code below (the form is blank, I'm trying to draw directly on the form).

Any ideas what I'm doing wrong here?

    public partial class Form1 : Form
    {
        [DllImport("gdi32.dll")]
        public static extern int StretchDIBits(IntPtr hdc, int XDest, int YDest,
           int nDestWidth, int nDestHeight, int XSrc, int YSrc, int nSrcWidth,
           int nSrcHeight, byte[] lpBits, [In] ref BITMAPINFO lpBitsInfo, uint iUsage,
          uint dwRop);

        public const uint SRCCOPY = 0xcc0020;
        public const uint DIB_RGB_COLORS = 0;

        [StructLayout(LayoutKind.Sequential)]
        public class BITMAPINFO
        {
            public Int32 biSize;
            public Int32 biWidth;
            public Int32 biHeight;
            public Int16 biPlanes;
            public Int16 biBitCount;
            public Int32 biCompression;
            public Int32 biSizeImage;
            public Int32 biXPelsPerMeter;
            public Int32 biYPelsPerMeter;
            public Int32 biClrUsed;
            public Int32 biClrImportant;
            public Int32 colors;
        };

        public Form1()
        {
            InitializeComponent();

            // Copy test data from an existing image to a byte[]
            Bitmap bmp = (Bitmap)Image.FromFile("C:\\test.jpg");
            BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
                                                                         ImageLockMode.ReadWrite, bmp.PixelFormat);
            byte[] pixels = new byte[bmpData.Stride * bmpData.Height];
            Marshal.Copy(bmpData.Scan0, pixels, 0, pixels.Length);
            bmp.UnlockBits(bmpData);

            this.DrawImage(pixels, CreateGraphics());
        }

        public void DrawImage(byte[] pixels, Graphics g)
        {
            // Create a BITMAPINFO object representing the image to be drawn
            // Image is 512 x 512, 8bpp
            BITMAPINFO bmi = new BITMAPINFO();
            bmi.biWidth = 512;
            bmi.biHeight = 512;
            bmi.biPlanes = 1;
            bmi.biBitCount = 8;
            bmi.biSizeImage = pixels.Length;

            // Draw the image using the graphics object
            StretchDIBits(g.GetHdc(), 0, 0, 512, 512, 0, 0, 512, 512, pixels, ref bmi, DIB_RGB_COLORS, SRCCOPY);
        }
    }

Thanks.
0
 
LVL 48

Expert Comment

by:AlexFM
ID: 16559580
public static extern int StretchDIBits(IntPtr hdc, int XDest, int YDest,
           int nDestWidth, int nDestHeight, int XSrc, int YSrc, int nSrcWidth,
           int nSrcHeight, IntPtr lpBits, [In] ref BITMAPINFO lpBitsInfo, uint iUsage,
          uint dwRop);

BITMAPINFO structure must be filled before calling to StretchDIBits. This structure describes to StretchDIBits how to display lpBits array on he screen. You can find here in EE examples of filling BITMAPINFO for RGB24 and monochrome bitmaps.
If bitmap has color palette, you need to implement some tricky (for C#) operation. Last element of BITMAPINFO is first palette element. Other palette elements must follow BITMAPINFO structure in the memory. Though such thing can be done using C#, Marshal class has enough functions for this, it is much beter to do this in C++.
0
 

Author Comment

by:TLevin10
ID: 16564324
Hi AlexFM,

After a bit of work, I got my StretchDIBits code working.  However, since the data is streaming as B/W 8bpp data, I should be seeing a black/white image (I believe).  However, the images are appearing as colorized (mostly blue) images.  I believe I need to implement the palette operations you talked about, but I'm not sure where to start (I'm not very experienced with the Marshal class or unmanaged code).  

I have created a basic B/W palette (array of RGBQUAD objects) but I am not sure how to attach this to the end of the BITMAPINFO header.  Any tips for how to attach the palette to the end of the BITMAPINFO object?  

An excerpt of my test code is below (Form with a Panel on it, drawing is done on the panel - creating my own pictureBox, in a sense):

public partial class Form1 : Form
    {
        [DllImport("gdi32.dll")]
        public static extern int StretchDIBits(IntPtr hdc, int XDest, int YDest,
           int nDestWidth, int nDestHeight, int XSrc, int YSrc, int nSrcWidth,
           int nSrcHeight, byte[] lpBits, [In] ref BITMAPINFO lpBitsInfo, uint iUsage,
          uint dwRop);

        private const uint DIB_RGB_COLORS = 0;
        private const uint SRCCOPY = 0xcc0020;
        private const uint SRCAND = 0x008800C6;
        private const int BI_RGB = 0;
        private const int GDI_ERROR = -1;

        [StructLayout(LayoutKind.Sequential)]
        public struct RGBQUAD
        {
            public int rgbBlue;
            public int rgbGreen;
            public int rgbRed;
            public int rgbReserved;
        };

        [StructLayout(LayoutKind.Sequential)]
        public 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)]
        public struct BITMAPINFO
        {
            public BITMAPINFOHEADER bmiHeader;
            public RGBQUAD bmiColors;
        }

        private byte[] pixels;
        private RGBQUAD[] r = new RGBQUAD[256];

        public Form1()
        {
            InitializeComponent();

            // Copy test data from an existing image to a byte[]
            //
            // This is just a byte[] of pixels stored to a file, not a Bitmap object of any kind
            //
            FileStream f = new FileStream("C:\\test.mcin", FileMode.Open);
            pixels = new byte[f.Length];
            f.Read(pixels, 0, pixels.Length);
            f.Close();

            //
            // Initialize B/W Palette
            //
            r = new RGBQUAD[256];
            for (int i = 0; i < 256; i++)
            {
                RGBQUAD rgb = new RGBQUAD();
                rgb.rgbBlue = i;
                rgb.rgbGreen = i;
                rgb.rgbRed = i;
                rgb.rgbReserved = i;

                r[i] = rgb;
            }
        }

        protected override void OnResize(EventArgs e)
        {
            this.panel1.Invalidate();
            base.OnResize(e);
        }

        public void DrawImage(byte[] pixels, Graphics g)
        {
            //
            // Create a BITMAPINFO object representing the image to be drawn
            //
            BITMAPINFOHEADER bmi = new BITMAPINFOHEADER();
            bmi.biSize = (uint) Marshal.SizeOf(bmi);
            bmi.biCompression = BI_RGB;
            bmi.biWidth = 512;
            bmi.biHeight = 512;
            bmi.biPlanes = 1;
            bmi.biBitCount = 8;
            bmi.biClrImportant = 0;
            bmi.biClrUsed = 0;
            bmi.biSizeImage = (uint) pixels.Length;

            BITMAPINFO b = new BITMAPINFO();
            b.bmiHeader = bmi;
            b.bmiColors = r[0];

            // Location to center the image on the panel
            Rectangle c = this.panel1.ClientRectangle;
            int xloc = c.Left + (c.Width - bmi.biWidth) / 2;
            int yloc = c.Top + (c.Height - bmi.biHeight) / 2;

            // Draw the image using the graphics object
            StretchDIBits(g.GetHdc(), xloc, yloc, bmi.biWidth, bmi.biHeight, 0, 0, bmi.biWidth, bmi.biHeight, pixels, ref b, DIB_RGB_COLORS, SRCCOPY);
        }

        private void panel1_Paint(object sender, PaintEventArgs e)
        {
            // Draw image every time the picturebox is repainted
            this.DrawImage(pixels, e.Graphics);
        }
    }

0
 
LVL 48

Expert Comment

by:AlexFM
ID: 16564705
BITMAPINFO is defined by the following way:
typedef struct tagBITMAPINFO {
  BITMAPINFOHEADER bmiHeader;
  RGBQUAD          bmiColors[1];
} BITMAPINFO, *PBITMAPINFO;

256 color palette starts from bmiColors and follows immediately after BITMAPINFO structure in memory. You need to reproduce the same in C# code. In C++ it looks like this:

BITMAPINFO * binfo = (BITMAPINFO*) new BYTE[sizeof(BITMAPINFO) + 256 * sizeof(RGBQUAD)];
// fill binfo.bmiHeader ...

// fill palette
for ( int i = 0; i < 256; i++ )
{
    binfo[i].rgbRed = i;
    binfo[i].rgbGreen = i;
    binfo[i].rgbBlue = i;
    binfo[i].rgbReserved = 0;
}

In C# you need to allocate memory using Marshal.AllocHGlobal class:

IntPtr p = Marshal.AlocHGlobal(Marshal.Siseof(typeof(BITMAPINFO) + 256 * Marshal.SizeOf(typeof(RGBQUAD)));

Now you can copy filled BITMAPINFO structure using Marshal.StructureToPtr Method. After this you need to write palette to the end of this structure, starting from bmiColors member. This is possible using Marshal.WriteByte method:

static void WriteByte (
      IntPtr ptr,                 // this is Ptr returned by AlocHGlobal
      int ofs,                     // offset starts from Marshal.Sizeof(BITMAPINFOHEADER) and incremented in loop
      unsigned char val)

Fill in loop incrementing offset value. Finally, pass ptr to StretchDIBits function.
Did you think about C++? It is really ugly to do such things in C#. For me it is easy to post here C++/CLI method which can be called from C# rather than write tricky C# code.

One more qiestion: are you sure that copying bytes to Bitmap is your real problem? If image processing gets 90% of time, you will end with the same result optimizing drawing code.
0
 

Author Comment

by:TLevin10
ID: 16565129
Hi AlexFM,

I see what you are saying with allocating the memory for the BITMAPINFOHEADER and then following that with the palette.  However, I'm not sure how to pass this as a pointer to the StretchDIBits function:

public static extern int StretchDIBits(IntPtr hdc, int XDest, int YDest,
           int nDestWidth, int nDestHeight, int XSrc, int YSrc, int nSrcWidth,
           int nSrcHeight, IntPtr lpBits, [In] ref BITMAPINFO lpBitsInfo, uint iUsage,
          uint dwRop);

Since I am passing the BITMAPINFO object as a reference, how is it reading the palette as well?  it seems they will be contiguous in memory, but since I'm passing the reference, isn't it still using the managed structure, not the memory location I assigned with AllocHGlobal?

I am passing the byte[] of pixel data as an IntPtr using:

            GCHandle h = GCHandle.Alloc(pixels, GCHandleType.Pinned);
            IntPtr ptr = h.AddrOfPinnedObject();

This makes sense, as I am passing a handle assigned to the managed object.  

For the BITMAPINFO object, however, I'm passing a reference, not a handle - it seems the unmanaged memory location will never get read because I am never actually using its location - what am I missing?

0
 
LVL 48

Expert Comment

by:AlexFM
ID: 16565209
You can change StretchDIBits C# definition:

public static extern int StretchDIBits(IntPtr hdc, int XDest, int YDest,
           int nDestWidth, int nDestHeight, int XSrc, int YSrc, int nSrcWidth,
           int nSrcHeight, byte[] lpBits, IntPtr lpBitsInfo, uint iUsage,
          uint dwRop);

In this case, instead of default structure marshalling provided by PInvoke, you must do all work yourself. lpBitsInfo must point to unmanaged memory block which contains BITMAPINFO structure followed by color palette. It is your responsibility to fill this memory block with information. My previous post describes how to do this.

If you have some experience in unmanaged C++ or C programming, you can understand all PInvoke and interoperability details reading Marshal class description. It contains all functions required to allocate unmanaged memory blocks, and exchange information between managed and unmanaged data.
PInvoke provides default marshalling for simple types, strings, arrays and structures. For more complicated cases we do the work using Marshal functions.
0
 

Author Comment

by:TLevin10
ID: 16565334
Makes sense - so instead of passing the structure and letting the PInvoke functions handle the marshalling, I do it myself and pass the pointer to the function.

I will work on this and hopefully I can get it going.
0
 
LVL 48

Expert Comment

by:AlexFM
ID: 16565398
Additional detail: declaring lpbits as byte[] in C# StretchDIBits definition is not what you need. I think that result of this is marshalling of bytes to unmanaged memory, exactly what you want to avoid. You need to decalre lpBits as IntPtr and pass to it direct pointer to managed memory. This is done using GCHangle AddrOfPinnedObject function - see one of my previous post.
0
 

Author Comment

by:TLevin10
ID: 16565525
public static extern int StretchDIBits(IntPtr hdc, int XDest, int YDest,
           int nDestWidth, int nDestHeight, int XSrc, int YSrc, int nSrcWidth,
           int nSrcHeight, IntPtr lpBits, IntPtr lpBitsInfo, uint iUsage, uint dwRop);

both passed as IntPtr, correct?
0
 
LVL 48

Expert Comment

by:AlexFM
ID: 16565867
Yes.
0
 
LVL 48

Expert Comment

by:AlexFM
ID: 16568112
Well, finally I found time to make demo project. You need Windows Forms applicaion and pictureBox1 control on it. Form1_Load creates BITMAPINFO and makes unmanaged pointer to pixels pixelsPtr. Form1_FormClosing releases them.
In your application you must create pixelsPtr and release it for every frame. BITMAPINFO must be allocated once, as in my sample. See full code in the following post.
0
 
LVL 48

Accepted Solution

by:
AlexFM earned 2000 total points
ID: 16568148
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace StretchDIBitsTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        [DllImport("Gdi32.dll", SetLastError = true)]
        private static extern int StretchDIBits(
            IntPtr hdc,                      // handle to DC
            int XDest,                    // x-coord of destination upper-left corner
            int YDest,                    // y-coord of destination upper-left corner
            int nDestWidth,               // width of destination rectangle
            int nDestHeight,              // height of destination rectangle
            int XSrc,                     // x-coord of source upper-left corner
            int YSrc,                     // y-coord of source upper-left corner
            int nSrcWidth,                // width of source rectangle
            int nSrcHeight,               // height of source rectangle
            IntPtr lpBits,                // bitmap bits
            IntPtr lpBitsInfo,            // bitmap data
            int iUsage,                   // usage options
            int dwRop);                   // raster operation code

        [DllImport("Gdi32.dll", SetLastError = true)]
        private static extern int SetStretchBltMode(IntPtr hdc, int iStretchMode);

        // BITMAPINFO with embedded BITMAPINFOHEADER to simplify the code
        struct BITMAPINFO
        {
            public int biSize;
            public int biWidth;
            public int biHeight;
            public short biPlanes;
            public short biBitCount;
            public int biCompression;
            public int biSizeImage;
            public int biXPelsPerMeter;
            public int biYPelsPerMeter;
            public int biClrUsed;
            public int biClrImportant;

            public int bmiColors;
        };

        struct RGBQUAD
        {
            public byte rgbBlue;
            public byte rgbGreen;
            public byte rgbRed;
            public byte rgbReserved;
        };

        const int COLORONCOLOR = 3;
        const int HALFTONE = 4;
        const int DIB_RGB_COLORS = 0;
        const int SRCCOPY = 0x00CC0020;

        const int imageWidth = 256;
        const int imageHeight = 256;

        byte[] pixels;
        IntPtr pixelsPtr;
        GCHandle pixelsHandle;

        IntPtr bitmapInfoPtr;

        private void Form1_Load(object sender, EventArgs e)
        {
            pixels = new byte[imageWidth * imageHeight];   // sample image

            for(int i = 0; i < pixels.Length; i++)    // fill it by some way
            {
                pixels[i] = (byte)i;
            }

            // make unmanaged pointer to pixels (you must do this for every image)
            pixelsHandle = GCHandle.Alloc(pixels, GCHandleType.Pinned);
            pixelsPtr = pixelsHandle.AddrOfPinnedObject();

            CreateBitmapInfo();
        }

        // allocate BITMAPINFO and color palette in unmanaged memory
        private void CreateBitmapInfo()
        {
            BITMAPINFO info = new BITMAPINFO();

            info.biSize = Marshal.SizeOf(typeof(BITMAPINFO)) - Marshal.SizeOf(typeof(Int32));  // sizeof BITMAPINFOHEADER
            info.biWidth = imageWidth;
            info.biHeight = imageHeight;
            info.biPlanes = 1;
            info.biBitCount = 8;
            info.biCompression = 0;     // BI_RGB
            info.biSizeImage = imageWidth * imageHeight;
            info.biXPelsPerMeter = 0;
            info.biYPelsPerMeter = 0;
            info.biClrUsed = 0;
            info.biClrImportant = 0;
            info.bmiColors = 0;         // to prevent warning

            // allocate memory
            bitmapInfoPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(BITMAPINFO)) +
                Marshal.SizeOf(typeof(RGBQUAD)) * 256);

            // copy BITMAPINFO
            Marshal.StructureToPtr(info, bitmapInfoPtr, false);

            int offset = Marshal.SizeOf(typeof(BITMAPINFO)) - Marshal.SizeOf(typeof(Int32));

            RGBQUAD color = new RGBQUAD();

            // copy color palette
            for ( int i = 0; i < 256; i++ )
            {
                color.rgbRed = (byte)i;
                color.rgbGreen = (byte)i;
                color.rgbBlue = (byte)i;
                color.rgbReserved = 0;

                IntPtr p = new IntPtr(bitmapInfoPtr.ToInt32() + offset);

                Marshal.StructureToPtr(color, p, false);

                offset += Marshal.SizeOf(typeof(RGBQUAD));
            }
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            // Release handle to pixels. You must do this for every frame
            pixelsHandle.Free();

            Marshal.FreeHGlobal(bitmapInfoPtr);
        }

        // Draw directly from pixels array to screen without additional copying
        private void pictureBox1_Paint(object sender, PaintEventArgs e)
        {
            IntPtr hdc = e.Graphics.GetHdc();

            //int oldStretchMode = SetStretchBltMode(hdc, HALFTONE);  // high quality
            int oldStretchMode = SetStretchBltMode(hdc, COLORONCOLOR);  // fast

            StretchDIBits(
                hdc,
                0,
                0,
                pictureBox1.Width,
                pictureBox1.Height,
                0,
                0,
                imageWidth,
                imageHeight,
                pixelsPtr,
                bitmapInfoPtr,
                DIB_RGB_COLORS,
                SRCCOPY);

            SetStretchBltMode(hdc, oldStretchMode);

            e.Graphics.ReleaseHdc(hdc);
        }
    }
}
1

Featured Post

Free Tool: Subnet Calculator

The subnet calculator helps you design networks by taking an IP address and network mask and returning information such as network, broadcast address, and host range.

One of a set of tools we're offering as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

This article introduced a TextBox that supports transparent background.   Introduction TextBox is the most widely used control component in GUI design. Most GUI controls do not support transparent background and more or less do not have the…
The article shows the basic steps of integrating an HTML theme template into an ASP.NET MVC project
This Micro Tutorial will teach you how to add a cinematic look to any film or video out there. There are very few simple steps that you will follow to do so. This will be demonstrated using Adobe Premiere Pro CS6.
This lesson discusses how to use a Mainform + Subforms in Microsoft Access to find and enter data for payments on orders. The sample data comes from a custom shop that builds and sells movable storage structures that are delivered to your property. …
Suggested Courses
Course of the Month20 days, 20 hours left to enroll

810 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question