8

The GDI+ generic error when saving a bitmap is obviously a common problem according to my research here on SO and the web. Given following simplified snippet:

byte[] bytes = new byte[2048 * 2048 * 2];

for (int i = 0; i < bytes.Length; i++)
{
    // set random or constant pixel data, whatever you want
}

Bitmap bmp = new Bitmap(2048, 2048, PixelFormat.Format16bppGrayScale);
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, 2048, 2048), ImageLockMode.ReadWrite, bmp.PixelFormat);
System.Runtime.InteropServices.Marshal.Copy(bytes, 0, bmpData.Scan0, 8388608);
bmp.UnlockBits(bmpData);
bmp.Save(@"name.bmp");

This results in the 0x80004005 generic error. The usual reason for this are said to be locks on components, but I do not see anything here I. Am I just blind? The path I am saving to exists, of course, only a empty bmp file is created (0B).

Background: I am getting pixel data from a camera driver that I transfer to .NET using a C++/CLI wrapper, so the Bitmap object above is returned by a function call. But since this small example already fails, I guess that there is nothing wrong with the adapter.

Any suggestions are highly appreciated!

simd
  • 83
  • 1
  • 6
  • 16-bit greyscale is not a valid bmp format maybe? – Roger Rowland Oct 31 '13 at 10:35
  • I haven't considered that, to be honest. Switching to 8bpp indexed created the file without any errors. The ultimate goal is to save the grayscale 16bpp image as PNG, but replacing the above by `bmp.Save(@"name.bmp", ImageFormat.Png);` did not work either. – simd Oct 31 '13 at 11:14
  • Have you tried specifying ImageFormat.Bmp when saving? See this answer: http://social.msdn.microsoft.com/Forums/vstudio/en-US/10252c05-c4b6-49dc-b2a3-4c1396e2c3ab/action?threadDisplayName=writing-a-16bit-grayscale-image – Ben Oct 31 '13 at 11:37
  • Also what platform are you on? If XP then likely 16BppGrayScale is not supported for saving. – Ben Oct 31 '13 at 11:41
  • @Ben: Win7 64. Also tried to specify `ImageFormat.Bmp`. See my last comment, obviously `Format16bppGrayscale` is not supported. But still, I am struggling to save the bitmap as a PNG with 16bpp. – simd Oct 31 '13 at 11:46
  • If saving as a PNG the intermediate bitmap should really be 32BitARGB. This should not significantly affect the file size if all the pixels are actually grey, due to the type of compression used. Just initialize RGB to the same values and don't forget A must be fully opaque (Cannot remember if this is zero or 255)! – Ben Oct 31 '13 at 12:25

2 Answers2

22
   Bitmap bmp = new Bitmap(2048, 2048, PixelFormat.Format16bppGrayScale);

GDI+ exceptions are rather poor, you'll have little hope to diagnose the two mistakes. The lesser one is your Save() call, it doesn't specify the ImageFormat you want to save. The default is PNG, not BMP as you hoped.

But the core one is PixelFormat.Format16bppGrayScale. When GDI+ was designed, long before .NET came around, everybody was still using CRTs instead of LCD monitors. CRTs were quite good at displaying a gamut of colors. Although good, there were no mainstream CRTs yet that were capable of display 65536 distinct gray colors. Most of all restricted by the DAC in the video adapter, the chip that converts the digital pixel value to an analog signal for the CRT. A DAC that can convert with 16-bit accuracy at 100 MHz or more wasn't technologically feasible yet. Microsoft gambled on display technology improving to make that possible someday so specified Format16bppGrayScale as a pixel format that might someday be available.

That did not happen. Rather the opposite, LCDs are significantly worse at color resolution. Typical LCD panels can only resolve 6 bits of a color rather than the 8 bits available from the pixel format. Getting to 16-bit color resolution is going to require a significant technological break-through.

So they guessed wrong and, since the pixel format isn't useful, GDI+ doesn't actually have an image encoder that can write a 16bpp grayscale image format. Kaboom when you try to save it to disk, regardless of the ImageFormat you pick.

16bpp grayscale is actually used, radiological imaging uses that pixel format. With very expensive displays to make it actually useful. Such equipment however invariable uses a custom image format to go with that, DICOM is the usual choice. GDI+ doesn't have a codec for it.

You'll need to go shopping for a library that supports the image format that your customer wants. Lead Tools is the thousand pound gorilla in that product segment.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Thank you for your detailed answer, always nice to get some background with the explanation. Point here is that the images are rather less used for display, but analysis by software afterwards. The images are from time-lapse microscopy and the 16bit are giving us quite more options than 8bit. So if I have to go with an extra lib, would WIC work here (already linking against it in the camera adapter), or do I have to fall back to libpng? – simd Oct 31 '13 at 12:54
  • 3
    You are just not committed to a supported image format at all in this case. The smart thing to do here is to just save the data in your own format. Nothing that BinaryWriter could not do for example. – Hans Passant Oct 31 '13 at 13:12
  • 1
    16 bit bitmaps are also useful for range data (like that generated by the Kinect, for example) – Eponymous Aug 15 '16 at 18:48
  • @simd Is there any library to make 16 bit grayscale images in c# ? – Rezaeimh7 Sep 30 '20 at 06:06
0

PixelFormat.Format32bppArgb seems to work for me on Ubuntu 20 using GDI.

var bitmapdata = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly,
                PixelFormat.Format32bppArgb);

The error I was getting was

System.ArgumentException: 'Parameter is not valid.'
at System.Drawing.SafeNativeMethods.Gdip.CheckStatus(Int32 status)
   at System.Drawing.Bitmap.LockBits(Rectangle rect, ImageLockMode flags, PixelFormat format, BitmapData bitmapData)
   at System.Drawing.Bitmap.LockBits(Rectangle rect, ImageLockMode flags, PixelFormat format)
Dan
  • 12,808
  • 7
  • 45
  • 54