1

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.

Tirsa
  • 33
  • 5
  • See the Bitmap builder method shown here: [How to make colors in a Palette transparent when drawing 8bpp Bitmaps](https://stackoverflow.com/a/65498663/7444103) (first snippet) using an indexed palette. You can of course use a different PixelFormat. – Jimi Sep 24 '21 at 16:38
  • 1
    rewind the stream -- also, you don't build the image correctly. there are usually headers you need to set up to describe the image contents. maybe even a palette. – Andy Sep 24 '21 at 16:39
  • Code shown in the post does not make much sense as bitmap is not a random collection of bytes... It is also unclear what is your actual goal - general "array of bytes" is not "an image" - so some [edit] to clarify what you need to achieve would be nice. – Alexei Levenkov Sep 24 '21 at 16:44
  • Bitmap is not just an array, it has *header*. In the simplest case you can *create* th bitmap of the desired size and then put *pixels* on it. – Dmitry Bychenko Sep 24 '21 at 16:49
  • Why not just get the picturebox's graphics and draw the pixels.. ? – Caius Jard Sep 24 '21 at 16:53
  • @Caius Jard, Could you please explain it more? There is not any desire for me to use memorystream. I only searched and found out that it is a way to achieve my goal. – Tirsa Sep 24 '21 at 16:55
  • Draw a linear gradient from 0 to 255, 256 pixels long ? https://learn.microsoft.com/en-us/dotnet/desktop/winforms/advanced/how-to-create-a-linear-gradient?view=netframeworkdesktop-4.8 (i'm not really sure if youre trying to draw a line or a square, tbh, but that link has some hopefully helpful info) – Caius Jard Sep 24 '21 at 17:06
  • I can confirm the problem when creating a `PixelFormat.Format8bppIndexed` image, even when using `.LockPixels()` instead of a memory stream. – John Alexiou Sep 24 '21 at 17:39

2 Answers2

3

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
    });
}
György Kőszeg
  • 17,093
  • 6
  • 37
  • 65
  • Thanks. What about a 16-bit gray scale data? The library written by you supports 16-bit igray scaled images? – Tirsa Sep 25 '21 at 10:57
  • @Tirsa: Yes it does. Please note though that 16bb grayscale bitmaps are not supported by `Graphics`, so they cannot be rendered or drawn by `Graphics.DrawImage`. I collected the restrictions [here](https://docs.kgysoft.net/drawing/?topic=html/M_KGySoft_Drawing_ImageExtensions_ConvertPixelFormat_1.htm). So you can you work with 16bpp grayscale bitmaps, but to [display](https://github.com/koszeggy/KGySoft.Drawing.Tools/blob/6365330180f9d07a4e4ada32cdcc86731503d582/KGySoft.Drawing.DebuggerVisualizers.Test/ViewModel/DebuggerTestFormViewModel.cs#L286) them you need to convert their pixel format. – György Kőszeg Sep 25 '21 at 15:39
  • Additional note on 16 bpp grayscale bitmaps. None of the built-in encoders can save them. The `SaveAs*` methods in [`ImageExtensions`](https://docs.kgysoft.net/drawing/?topic=html/Methods_T_KGySoft_Drawing_ImageExtensions.htm) support them, though they first convert them to 8 or 24 bpp images before saving. To preserve the actual 16bpp content you can use the [`BitmapDataExtensions.Save`](https://docs.kgysoft.net/drawing/?topic=html/M_KGySoft_Drawing_Imaging_BitmapDataExtensions_Save.htm) method, which uses a custom raw format. – György Kőszeg Sep 25 '21 at 15:49
  • I am trying `Emgu CV` package to use its `Image Box` instead of `picture box` to gain displaying 16bpp images. Am I on right way? – Tirsa Sep 25 '21 at 16:37
  • I mean this: https://www.emgu.com/wiki/index.php/ImageBox – Tirsa Sep 25 '21 at 16:39
  • Any WinForms/GDI+ controls must internally convert a 16bpp grayscale image to be able to display by using `Graphics`. If `ImageBox` features fit for your needs just use it. I also created my `PictureBox` [alternative](https://github.com/koszeggy/KGySoft.Drawing.Tools/blob/Development/KGySoft.Drawing.ImagingTools/View/Controls/ImageViewer.cs) that supports all pixel formats, zooming, panning, can turn on/off smoothing (performing the smoothing asynchronously for large images) and supports all of these for metafiles, too. – György Kőszeg Sep 25 '21 at 18:09
  • Wonderful! Other image formats like RGB have to be converted to 8-bit or this restriction is only for grayscale ones? Can I use RGB 16-bit to display a grayscale 16-bit? – Tirsa Sep 26 '21 at 04:30
  • Your designed `PictureBox` is able to display 16-bit images? How Can I have DLL file of your `view Controls` to be imported in my project? – Tirsa Sep 26 '21 at 04:37
  • I could not import your dll files into `WinForm ToolBox` to use `ImageViewer`. Could you please provide me with a beginner guide about how to use `ImageViewer` in my project? – Tirsa Sep 26 '21 at 04:51
  • My `ImageViewer` was just an example, it is not available as an importable package (yet). But the source is there, and the license allows you to use it with proper attribution. But as I [demonstrated](https://github.com/koszeggy/KGySoft.Drawing.Tools/blob/6365330180f9d07a4e4ada32cdcc86731503d582/KGySoft.Drawing.DebuggerVisualizers.Test/ViewModel/DebuggerTestFormViewModel.cs#L286) above, you can always create a converted image to display it in a simple `PictureBox` or EmguCV's `ImageBox`. – György Kőszeg Sep 26 '21 at 12:48
1

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));
    }
}
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • it works fine but it is very time-consuming in my app. if w = 3072 it takes 3400ms to be executed. Is there any elegant way to reduce its time execution? – Tirsa Sep 25 '21 at 08:11