Tirsa
Tirsa

Reputation: 33

Show an array of bytes as an image on a form

I wrote some code to show an array of bytes as an image. There is an array of bytes in which every element represents a value of 8-bit gray scale image. Zero equals the most black and 255 does the most white pixel. My goal is to convert this w*w-pixel gray-scale image to some thing accepted by pictureBox1.Image. This is my code:

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

        private void button1_Click(object sender, EventArgs e)
        {
            int i = 0, j = 0, w = 256;
            byte[] rawIm = new byte[256 * 256];
            for(i = 0; i < w; ++i)
            {
                for (j = 0; j < w; ++j)
                {
                    rawIm[i * w + j] = (byte)j; // BitConverter.GetBytes(j);
                }
            }

            MemoryStream mStream = new MemoryStream();
            mStream.Write(rawIm, 0, Convert.ToInt32(rawIm.Length));
            Bitmap bm = new Bitmap(mStream, false);// the error occurs here
            mStream.Dispose();
            pictureBox1.Image = bm;

        }
    }
}

However I get this error: Parameter is not valid.
The error snapshot
where is my mistake?

EDIT: In next step I am going to display 16-bit grayscale images.

Upvotes: 1

Views: 1594

Answers (2)

Gy&#246;rgy Kőszeg
Gy&#246;rgy Kőszeg

Reputation: 18013

The Bitmap(Stream, bool) constructor expects a stream with an actual image format (eg. PNG, GIF, etc.) along with header, palette, and possibly compressed image data.

To create a Bitmap from raw data, you need to use the Bitmap(int width, int height, int stride, PixelFormat format, IntPtr scan0) constructor, but that is also quite inconvenient because you need a pinned raw data that you can pass as scan0.

The best if you just create an 8bpp bitmap with grayscale palette and set the pixels manually:

var bmp = new Bitmap(256, 256, PixelFormat.Format8bppIndexed);

// making it grayscale
var palette = bmp.Palette;
for (int i = 0; i < 255; i++)
    palette.Entries[i] = Color.FromArgb(i, i, i);
bmp.Palette = palette;

Now you can access its raw content as bytes where 0 is black and 255 is white:

var bitmapData = bmp.LockBits(new Rectangle(Point.Empty, bmp.Size), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
for (int y = 0; y < bitmapData.Height; y++)
{
    for (int x = 0; x < bitmapData.Width; x++)
    {
        unsafe
        {
            ((byte*) bitmapData.Scan0)[y * bitmapData.Stride + x] = (byte)x;
        }
    }
}
bmp.UnlockBits(bitmapData);

The result image: 8bpp indexed grayscale image with horizontal gradient

But if you don't want to use unsafe code, or you want to set pixels by colors, you can use this library (disclaimer: written by me) that supports efficient manipulation regardless of the actual PixelFormat. Using that library the last block can be rewritten like this:

using (IWritableBitmapData bitmapData = bmp.GetWritableBitmapData())
{
    IWritableBitmapDataRow row = bitmapData.FirstRow;
    do
    {
        for (int x = 0; x < bitmapData.Width; x++)
            row[x] = Color32.FromGray((byte)x); // this works for any pixel format
            // row.SetColorIndex(x, x); // for the grayscale 8bpp bitmap created above
    } while (row.MoveNextRow());
}

Or like this, using Parallel.For (this works only because in your example all rows are the same so the image is a horizontal gradient):

using (IWritableBitmapData bitmapData = bmp.GetWritableBitmapData())
{
    Parallel.For(0, bitmapData.Height, y =>
    {
        var row = bitmapData[y];
        for (int x = 0; x < bitmapData.Width; x++)
            row[x] = Color32.FromGray((byte)x); // this works for any pixel format
            // row.SetColorIndex(x, x); // for the grayscale 8bpp bitmap created above
    });
}

Upvotes: 3

Guru Stron
Guru Stron

Reputation: 142233

As said in the comments - bitmap is not just an array. So to reach your goal you can create bitmap of needed size and set pixels with Bitmap.SetPixel:

Bitmap bm = new Bitmap(w, w);
for(var i = 0; i < w; ++i)
{
    for (var j = 0; j < w; ++j)
    {
        bm.SetPixel(i,j, Color.FromArgb(j, j, j));
    }
}

Upvotes: 1

Related Questions