14

I will be receiving some raw data that will be stored in a byte array, where each 2 bytes is a pixel value (16 bits/px). To start with, the array will contain 100x100*2 bytes (enough for a 100x100 pixel image). I would like to display this data in the Form window. Eventually, I would like to refresh the image with the new data to make it look like a video stream. No strict frame rate is required. How can this be done? Any code examples in C#?

EDIT: After some suggestions and reviews of tens of similar questions I still can not get this going. Here's the general idea of what I am trying to do, but the image is not displayed in the picture box on the form. What is specifically wrong with my implementation and how to fix it?

// array of data I collected
byte[] dataArray = new byte[100 * 100 * 2]; 
//create a pointer to the data
IntPtr hglobal = Marshal.AllocHGlobal(100 * 100 * 2);

// copy my array to global
Marshal.Copy(dataArray, 0, hglobal, dataArray.Length);
// create a bitmap: 100x100 pixels, 2bytes/pixel, 16bitgrayscale
Bitmap newBitmap = new Bitmap(100, 100, 2 * 100, PixelFormat.Format16bppGrayScale, hglobal);

// display bitmap
pictureBox1.Image = newBitmap;

// free the memory
Marshal.FreeHGlobal(hglobal);
Nazar
  • 820
  • 5
  • 13
  • 36
  • you may want to take a look at https://stackoverflow.com/questions/16622408/set-the-pixels-of-image-to-the-values-stored-in-an-array-of-integers and https://stackoverflow.com/questions/16130248/pixel-data-to-image – mcy Aug 29 '15 at 21:05
  • Can you iterate over array and use `Bitmap.SetPixel()`? – MahanGM Aug 29 '15 at 21:11
  • as @Eldarien said, you need to use `Format48bppRgb` – Jony Adamit Sep 14 '15 at 09:35

2 Answers2

9

The main problem is that PixelFormat.Format16bppGrayScale is not supported (at least on my Win 8.1 x64 system). So you have to convert image to rgb before displaying:

private void Form1_Load(object sender, EventArgs e)
{
    //Create pixel data to put in image, use 2 since it is 16bpp
    Random r = new Random();
    int width = 100;
    int height = 100;
    byte[] pixelValues = new byte[width * height * 2];
    for (int i = 0; i < pixelValues.Length; ++i)
    {
        // Just creating random pixel values for test
        pixelValues[i] = (byte)r.Next(0, 256);
    }

    var rgbData = Convert16BitGrayScaleToRgb48(pixelValues, width, height);
    var bmp = CreateBitmapFromBytes(rgbData, width, height);

    // display bitmap
    pictureBox1.Image = bmp;
}

private static byte[] Convert16BitGrayScaleToRgb48(byte[] inBuffer, int width, int height)
{
    int inBytesPerPixel = 2;
    int outBytesPerPixel = 6;

    byte[] outBuffer = new byte[width * height * outBytesPerPixel];
    int inStride = width * inBytesPerPixel;
    int outStride = width * outBytesPerPixel;

    // Step through the image by row
    for (int y = 0; y < height; y++)
    {
        // Step through the image by column
        for (int x = 0; x < width; x++)
        {
            // Get inbuffer index and outbuffer index
            int inIndex = (y * inStride) + (x * inBytesPerPixel);
            int outIndex = (y * outStride) + (x * outBytesPerPixel);

            byte hibyte = inBuffer[inIndex + 1];
            byte lobyte = inBuffer[inIndex];

            //R
            outBuffer[outIndex] = lobyte;
            outBuffer[outIndex + 1] = hibyte;

            //G
            outBuffer[outIndex + 2] = lobyte;
            outBuffer[outIndex + 3] = hibyte;

            //B
            outBuffer[outIndex + 4] = lobyte;
            outBuffer[outIndex + 5] = hibyte;
        }
    }
    return outBuffer;
}

private static Bitmap CreateBitmapFromBytes(byte[] pixelValues, int width, int height)
{
    //Create an image that will hold the image data
    Bitmap bmp = new Bitmap(width, height, PixelFormat.Format48bppRgb);

    //Get a reference to the images pixel data
    Rectangle dimension = new Rectangle(0, 0, bmp.Width, bmp.Height);
    BitmapData picData = bmp.LockBits(dimension, ImageLockMode.ReadWrite, bmp.PixelFormat);
    IntPtr pixelStartAddress = picData.Scan0;

    //Copy the pixel data into the bitmap structure
    System.Runtime.InteropServices.Marshal.Copy(pixelValues, 0, pixelStartAddress, pixelValues.Length);

    bmp.UnlockBits(picData);
    return bmp;
}

Idea was taken from this thread.

Eldarien
  • 813
  • 6
  • 10
  • Finally, I have something displayed on the screen! Thank you for the help. Is this, in general, a good solution? My images will be 2048x2048 pix with incoming frequency of about 10 fps. I am afraid that so much data manipulation just to create a bitmap, will not be able to catch up with the data flow. What do you say? – Nazar Sep 14 '15 at 12:30
  • Well, converting bytes to 48bpp is as fast as it can be, and creating bitmap can not be faster ether. So you have to try to feed it with 10 images per second and see how it goes. The part that I would change is where the final drawing is done (Image control) - drawing bitmap yourself on form's OnPaint event and using double buffering should help. – Eldarien Sep 14 '15 at 12:47
  • Could you, please, explain what you mean by _drawing bitmap yourself on form's OnPaint event_? I currently have a pictureBox component on my form. Isn't that what should be used to display images? – Nazar Sep 14 '15 at 12:58
  • PictureBox is for static images, its repainting will be slow. Better option will be using Panel as a base class for custom control that will do the drawing. Like here: http://stackoverflow.com/a/6712720/635410 , but ignore image morphing stuff. – Eldarien Sep 14 '15 at 13:11
  • So, I removed the pictureBox and placed a panel on my form. But is it necessary to make this panel as a class? How do I invoke the _paint_ event? – Nazar Sep 14 '15 at 14:45
  • Yes, you have to make it as a class to be able to override its OnPaint event. To force repaint you can use Refresh method: https://msdn.microsoft.com/en-us/library/system.windows.forms.control.refresh%28v=vs.110%29.aspx – Eldarien Sep 14 '15 at 14:56
  • Anyway, drawing images on a panel is a different thing from what was asked here, so lets stop. – Eldarien Sep 14 '15 at 14:57
  • A lot of optimizations can be done for your concrete case. But that would be another story, so if you need assistance, open another issue with the your exact needs and we'll be glad to help. For this one I think you should accept Eldarien's answer. – Ivan Stoev Sep 14 '15 at 17:53
  • Eldarien, There is a remark below [PixelFormat Enumeration](https://msdn.microsoft.com/en-us/library/system.drawing.imaging.pixelformat(v=vs.110).aspx) and it says that for 48RGB format the image is actually displayed as 8bit image. So, it does not really make sense for me to supply data as 16bit, as it only increases the amount of operations but does not produce a higher bitdepth image. Did I understand that right? – Nazar Oct 27 '15 at 13:35
  • @IvanStoev Ok, I finally got to the point when I need to do some performance optimization. If you have comments, please, share them with me [here](http://stackoverflow.com/questions/33401235/what-is-the-optimal-way-to-display-images-fast-c) – Nazar Oct 28 '15 at 20:43
5

Use this Bitmap constructor:

public Bitmap(
    int width,
    int height,
    int stride,
    PixelFormat format,
    IntPtr scan0
)

You pass it the shape of your bitmap, the stride (how many bytes per line, including padding), pixel format and the pixel data as a void * pointer. You can create the latter with Marshal.AllocHGlobal and fill it in as normal with pointer operations. Don't forget to free this memory after you create your bitmap.

Edit to account for updated question:

Simply call IntPtr.ToPointer() to get back a pointer. If you're familiar with C, the rest should be cake:

var p=(char *)hglobal.ToPointer();  // bad name by the way, it's not a handle, it's a pointer
p[0]=0;                             // access it like any normal pointer

However, you can use the Marshaller to copy memory for you from managed to unmanaged (getting your hands dirty is usually frowned upon in C#):

Marshal.Copy(dataArray, 0, hglobal, dataArray.Length); // again, terrible name

A Bitmap is an Image (as in, it derives from it), however you're using Graphics.DrawImage() wrong. As the error says, it's not a static method, you draw it to a specific graphic context. Now what that graphic context is, that's up to you:

  • If you want to paint it in response to WM_PAINT, use the Paint event -- it provides you with a special Graphics object set up with clipping and everything as instructed by the windowing system.
  • If you want to paint it on a bitmap to be later displayed somehow (the common use, also called double buffering), use Graphics.FromImage() on the source bitmap then draw your bitmap over it.

You can (and should) delete your virtual memory buffer as soon as you get the result back from the Bitmap constructor. Don't leak memory, use a try..finally construct.

Blindy
  • 65,249
  • 10
  • 91
  • 131
  • Thank you. Would you, please, give a little more information on how to work with this Bitmap. What to do with it. How to display the image? I am very new to this. May be you have some reference to an example? – Nazar Aug 31 '15 at 21:02
  • You get a `Graphics` context from an image or from the framework's `Paint` event and draw your image with `Graphics.DrawImage`. – Blindy Sep 01 '15 at 01:05
  • Just updated my question. This is what I came up with so far, and this is where my power ends. Please, help. – Nazar Sep 01 '15 at 18:48
  • 1
    Updated by answer too, hope that helps. You really should read an in-depth article on GDI/GDI+ though, most beginners find the fact that they don't own the user's screen entirely confusing. – Blindy Sep 01 '15 at 19:51
  • Ok, thank you. I will keep working on it. Regarding the "graphic context" - I just want to display the image in the window app form (in the PictureBox?), or on a separate form. Is it possible to display the image when I want instead of as a response to an event? – Nazar Sep 01 '15 at 20:14
  • Yes, but it would be useless to do it like that. The first time a redraw happens (windows moving for example) it would be gone. – Blindy Sep 01 '15 at 21:43
  • Then, what is the proper way to do it? I wish the sequence of images was constantly updating on the form, similar to video. – Nazar Sep 02 '15 at 11:55