使用LockBits从内存创建位图时,GDI+通用错误保存位图

8
保存位图时出现的GDI+通用错误显然是一个常见问题,根据我在SO和网络上的研究。以下是一个简化的片段:
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");

这将导致0x80004005通用错误。据说这种情况通常是由于组件锁定引起的,但我在这里没有看到任何东西。难道我只是瞎了吗?我要保存的路径当然存在,只创建了一个空的bmp文件(0B)。
背景:我从相机驱动程序获取像素数据,并使用C++/CLI包装器将其传输到.NET,因此上面的Bitmap对象是通过函数调用返回的。但由于这个小例子已经失败了,所以我想适配器没有问题。
非常感谢您提供任何建议!

16位灰度不是有效的BMP格式,可能吗? - Roger Rowland
说实话,我还没有考虑过这个问题。将图像转换为8bpp索引色模式后,文件生成时没有出现任何错误。最终目标是将灰度16bpp图像保存为PNG格式,但是用bmp.Save(@"name.bmp", ImageFormat.Png);替换上述代码也无法实现。 - simd
你尝试过在保存时指定ImageFormat.Bmp吗?请参考这个答案:http://social.msdn.microsoft.com/Forums/vstudio/en-US/10252c05-c4b6-49dc-b2a3-4c1396e2c3ab/action?threadDisplayName=writing-a-16bit-grayscale-image - Ben
你在哪个平台上?如果是XP,则可能不支持16BppGrayScale保存。 - Ben
@Ben:Win7 64。我还尝试指定ImageFormat.Bmp。请看我的最后一条评论,显然不支持Format16bppGrayscale。但是,我仍在努力将位图保存为16bpp的PNG格式。 - simd
如果保存为PNG格式,则中间位图应该是32BitARGB。如果所有像素实际上都是灰色的,由于使用的压缩类型,这不应显着影响文件大小。只需将RGB初始化为相同的值,并且不要忘记A必须完全不透明(无法记住这是零还是255)! - Ben
2个回答

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

GDI+ 异常相当糟糕,你很难诊断出这两个错误。较小的错误是你的 Save() 调用中没有指定你想要保存的图像格式。默认值是 PNG,而不是你想要的 BMP。

但核心问题是 PixelFormat.Format16bppGrayScale。在设计 GDI+ 时,早在 .NET 出现之前,每个人仍然使用 CRT 而不是 LCD 显示器。CRT 在显示色彩方面非常出色。虽然很好,但尚没有主流的 CRT 可以显示 65536 种不同的灰度颜色。所有受到视频适配器中 DAC 的限制,该芯片将数字像素值转换为 CRT 的模拟信号。一款可以以 100 MHz 或更高精度进行转换的 DAC 还不可行。微软打赌显示技术会有所改进,有一天可能会实现 Format16bppGrayScale 指定的像素格式。

但事实并非如此。相反,LCD 在色彩分辨率方面要差得多。典型的液晶面板只能分辨出颜色的 6 位,而不是像素格式提供的 8 位。要达到 16 位的色彩分辨率需要一个重大的技术突破。

所以他们猜错了,由于像素格式无用,因此 GDI+ 实际上没有一个可以编写 16bpp 灰度图像格式的图像编码器。尝试将其保存到磁盘时会出现问题,无论你选择哪种 ImageFormat。

实际上使用了 16bpp 灰度,放射学成像使用该像素格式。为了使它真正有用,需要非常昂贵的显示设备。然而,这种设备通常使用与之配套的自定义图像格式,DICOM 是常见的选择。GDI+ 中没有其编解码器。

您需要寻找一个支持您客户所需图像格式的库。Lead Tools 是该产品领域的巨头。


感谢您详细的回答,解释时了解一些背景总是很好的。这里的重点是图像不太用于显示,而是之后由软件进行分析。这些图像来自时间-lapse显微镜,16位比8位给我们提供了更多的选择。因此,如果我必须使用额外的库,那么WIC是否适用于此(已经在相机适配器中链接它),还是我必须退回到libpng? - simd
3
在这种情况下,你根本没有致力于使用一个受支持的图像格式。聪明的做法是只需将数据保存在自己的格式中,这并不比BinaryWriter难。 - Hans Passant
1
16位比特图像对于范围数据非常有用(例如由Kinect生成的数据)。 - Eponymous
@simd 在C#中是否有任何库可以制作16位灰度图像? - Rezaeimh7

0

PixelFormat.Format32bppArgb 在我使用 GDI 在 Ubuntu 20 上似乎是可用的。

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

我遇到的错误是

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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接