5

I am trying to generate 16bit grayscale Bitmap in C# from a random data.But it crashed on Marshal.Copy.

Here is my code:

   Bitmap b16bpp;
    private void GenerateDummy16bitImage()
    {

        b16bpp = new Bitmap(IMAGE_WIDTH, IMAGE_HEIGHT, System.Drawing.Imaging.PixelFormat.Format16bppGrayScale);

        var rect = new Rectangle(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
        var bitmapData = b16bpp.LockBits(rect, ImageLockMode.WriteOnly, b16bpp.PixelFormat);
        // Calculate the number of bytes required and allocate them.
        var numberOfBytes = bitmapData.Stride * IMAGE_HEIGHT * 2;
        var bitmapBytes = new short[numberOfBytes];
        // Fill the bitmap bytes with random data.
        var random = new Random();
        for (int x = 0; x < IMAGE_WIDTH; x++)
        {
            for (int y = 0; y < IMAGE_HEIGHT; y++)
            {

                var i = ((y * IMAGE_WIDTH) + x) * 2; // 16bpp

                // Generate the next random pixel color value.
                var value = (short)random.Next(5);

                bitmapBytes[i] = value;         // BLUE
                bitmapBytes[i + 1] = value;     // GREEN
                bitmapBytes[i + 2] = value;     // RED
              //  bitmapBytes[i + 3] = 0xFF;      // ALPHA
            }
        }
        // Copy the randomized bits to the bitmap pointer.
        var ptr = bitmapData.Scan0;
        Marshal.Copy(bitmapBytes, 0, ptr, numberOfBytes);//crashes here

        // Unlock the bitmap, we're all done.
        b16bpp.UnlockBits(bitmapData);

        b16bpp.Save("random.bmp", ImageFormat.Bmp);
        Debug.WriteLine("saved");
    }

The exception is:

An unhandled exception of type 'System.AccessViolationException' occurred in mscorlib.dll

This is not my code.I found it in relation to 32bit Bitmaps and modified.But I guess I have missed something as I am pretty new to C#.

Basically,all I need is to wrap into BitmapData an arrays of shorts.

Michael IV
  • 11,016
  • 12
  • 92
  • 223
  • Can you elaborate on "it crashed"? – Blorgbeard Nov 03 '14 at 19:43
  • Not that it would cause a crash, but `Format16bppGrayScale` means that each pixel does not have separate RGB components, just a single 16-bit value (use `UInt16` rather than the signed `short` type). – Dai Nov 03 '14 at 19:45
  • @Dai if I do what you suggest the Marshal.Copy function complaints of wrong argument as it can't handle Int16[] . – Michael IV Nov 03 '14 at 19:53
  • 1
    Btw,I can't understand those dudes who voted to close the question.What is wrong with it????I have searched the web for 2 3 hours to find the resolution and found none. – Michael IV Nov 03 '14 at 19:55
  • 1
    How long you've searched has nothing to do with how good your question is. I voted to close because (at the time) you had given us no details except "it crashes, here's my code". – Blorgbeard Nov 03 '14 at 20:14
  • Curious: What do you plan to do with 16-bit grayscale images?? No normal hardware can display it.. – TaW Nov 03 '14 at 20:23
  • Uploading it to GPU to use as height map. – Michael IV Nov 03 '14 at 20:56

2 Answers2

29

This works for System.Drawing.Imaging.PixelFormat.Format16bppGrayScale:

    private static void SaveBmp(Bitmap bmp, string path)
    {
        Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);

        BitmapData bitmapData = bmp.LockBits(rect, ImageLockMode.ReadOnly, bmp.PixelFormat);

        var pixelFormats = ConvertBmpPixelFormat(bmp.PixelFormat);

        BitmapSource source = BitmapSource.Create(bmp.Width,
                                                  bmp.Height,
                                                  bmp.HorizontalResolution,
                                                  bmp.VerticalResolution,
                                                  pixelFormats,
                                                  null,
                                                  bitmapData.Scan0,
                                                  bitmapData.Stride * bmp.Height,
                                                  bitmapData.Stride);

        bmp.UnlockBits(bitmapData);


        FileStream stream = new FileStream(path, FileMode.Create);

        TiffBitmapEncoder encoder = new TiffBitmapEncoder();

        encoder.Compression = TiffCompressOption.Zip;
        encoder.Frames.Add(BitmapFrame.Create(source));
        encoder.Save(stream);

        stream.Close();
    }

    private static System.Windows.Media.PixelFormat ConvertBmpPixelFormat(System.Drawing.Imaging.PixelFormat pixelformat)
    {
        System.Windows.Media.PixelFormat pixelFormats = System.Windows.Media.PixelFormats.Default;

        switch (pixelformat)
        {
            case System.Drawing.Imaging.PixelFormat.Format32bppArgb:
                pixelFormats = PixelFormats.Bgr32;
                break;

            case System.Drawing.Imaging.PixelFormat.Format8bppIndexed:
                pixelFormats = PixelFormats.Gray8;
                break;

            case System.Drawing.Imaging.PixelFormat.Format16bppGrayScale:
                pixelFormats = PixelFormats.Gray16;
                break;
        }

        return pixelFormats;
    }
Karsten
  • 306
  • 3
  • 2
7

I have corrected some of your mistakes (mostly wrong sizes). But it will still crash on b16bpp.Save(), because GDI+ does not support saving 16bit grayscale images.

Bitmap b16bpp;
private void GenerateDummy16bitImage()
{

    b16bpp = new Bitmap(IMAGE_WIDTH, IMAGE_HEIGHT, System.Drawing.Imaging.PixelFormat.Format16bppGrayScale);

    var rect = new Rectangle(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
    var bitmapData = b16bpp.LockBits(rect, ImageLockMode.WriteOnly, b16bpp.PixelFormat);
    // Calculate the number of bytes required and allocate them.
    var numberOfBytes = bitmapData.Stride * IMAGE_HEIGHT;
    var bitmapBytes = new short[IMAGE_WIDTH * IMAGE_HEIGHT];
    // Fill the bitmap bytes with random data.
    var random = new Random();
    for (int x = 0; x < IMAGE_WIDTH; x++)
    {
        for (int y = 0; y < IMAGE_HEIGHT; y++)
        {

            var i = ((y * IMAGE_WIDTH) + x); // 16bpp

            // Generate the next random pixel color value.
            var value = (short)random.Next(5);

            bitmapBytes[i] = value;         // GRAY
        }
    }
    // Copy the randomized bits to the bitmap pointer.
    var ptr = bitmapData.Scan0;
    Marshal.Copy(bitmapBytes, 0, ptr, bitmapBytes.Length);

    // Unlock the bitmap, we're all done.
    b16bpp.UnlockBits(bitmapData);

    b16bpp.Save("random.bmp", ImageFormat.Bmp);
    Debug.WriteLine("saved");
}

Explanation of my changes:

  • bitmapData.Stride is already IMAGE_WIDTH * BytesPerPixel so you don't need to multiply by 2
  • as you declared bitmapBytes as short[] it has to have the size of the image in pixels not in bytes
  • that means you also do not need to multiply i by 2
  • since you have a grayscale image it does not have a blue, green and red channel, but one single 16bit gray channel
  • Marshal.Copy takes the length in "array units" not in bytes

All in all you tried to copy an array 8 times to large into the bitmap.

Community
  • 1
  • 1
Karsten
  • 1,814
  • 2
  • 17
  • 32
  • Well,in fact the "save to file" part was for debug purposes :) I really need to use that Bitmap inside the application. Appreciate your help.Thanks. – Michael IV Nov 03 '14 at 20:57