将 BITMAP 结构转换为 BitmapImage/BitmapSource 兼容字节数组?

Converting a BITMAP structure to a BitmapImage/BitmapSource compatible byte array?

我有一个包含 BITMAP 结构的变量。这个结构定义如下。

[StructLayoutAttribute(LayoutKind.Sequential)]
public struct BITMAP
{
    public Int32 Type;
    public Int32 Width;
    public Int32 Height;
    public Int32 WidthBytes;
    public UInt16 Planes;
    public UInt16 BitsPixel;
    public IntPtr Bits;
}

这个结构没有问题。当我尝试使用 GDI GetObject API.

获取位图时,它会填充真实数据

当我尝试将其转换为字节数组并再次返回时出现问题。

下面您将看到我如何将结构转换为字节数组。这再次正常工作,我在数组中看到数据。

var bitmap = new BITMAP();
var bufferSize = Marshal.SizeOf(bitmap);

GetObject(bitmapHandle, bufferSize, out bitmap);

var bytes = new byte[bitmap.WidthBytes * bitmap.Height];
Marshal.Copy(bitmap.Bits, bytes, 0, bytes.Length);

DeleteObject(bitmapHandle);

//"bytes" now contains the byte array of pixels.

现在,这就是一切出错的地方。当我稍后尝试将此给定数组转换回 BitmapImage 时,我得到 NotSupportedException.

这是我转换回来的代码。

var image = new BitmapImage();
using (var stream = new MemoryStream(bytes))
{
    stream.Position = 0;

    image.BeginInit();
    image.CreateOptions = BitmapCreateOptions.None;
    image.CacheOption = BitmapCacheOption.OnDemand;
    image.UriSource = null;
    image.StreamSource = stream;
    image.EndInit(); //it throws the exception here
}

image.Freeze();

异常消息是No imaging component suitable to complete this operation was found

我试过在网上寻找解决方案,但没有成功。我似乎无法弄清楚我做错了什么。我假设它在从 BITMAP 到字节数组的转换过程中,但我不知道。

需要注意的是,BitmapSource 也可以。

如果你只需要一个 BitmapSource(或一个 ImageSource),你可以使用 GDI 的 CreateBitmapIndirect function and WPF interop's Imaging.CreateBitmapSourceFromHBitmap Method ,像这样:

  static BitmapSource FromBITMAP(ref BITMAP bmp)
  {
        Int32Rect rect = new Int32Rect(0, 0, bmp.Width, bmp.Height);
        IntPtr hbitmap = CreateBitmapIndirect(ref bmp);
        if (hbitmap == IntPtr.Zero)
            return null;

        try
        {
            return Imaging.CreateBitmapSourceFromHBitmap(hbitmap, IntPtr.Zero, rect, BitmapSizeOptions.FromEmptyOptions());
        }
        finally
        {
            DeleteObject(hbitmap);
        }
  }

  [DllImport("gdi32.dll", SetLastError = true)]
  public static extern IntPtr CreateBitmapIndirect(ref BITMAP lpbm);

  [DllImport("gdi32.dll", SetLastError = true)]
  public static extern bool DeleteObject(IntPtr hObject);

创建位图数据数组失败。您正在计算数组大小只是乘以

bitmap.WidthBytes * bitmap.Height

这不是 BitmapImage 的预期来源,因为 BitmapImage 来源需要是完整的位图文件结构,包括像素数据之前的 54 个字节。

更多关于位图结构的信息在这里: https://www.daubnet.com/en/file-format-bmp

也许您还需要知道像素数据区域的大小受 "padding" 一些位于每个扫描线末尾的额外字节添加到完整的 Int32 块以加速位图加载的影响。

这里出了点问题。 BITMAP 不是对象,根据 GetObject API 文档,声称获取带像素的字节数组的代码是不正确的:

If hgdiobj is a handle to a bitmap created by calling CreateDIBSection, and the specified buffer is large enough, the GetObject function returns a DIBSECTION structure. In addition, the bmBits member of the BITMAP structure contained within the DIBSECTION will contain a pointer to the bitmap's bit values. If hgdiobj is a handle to a bitmap created by any other means, GetObject returns only the width, height, and color format information of the bitmap. You can obtain the bitmap's bit values by calling the GetDIBits or GetBitmapBits function.

所以让我们从头开始。据我所知,您的代码以名为 bitmapHandle 的东西开始,我假设它是来自某处的 HBITMAP。如果是这样的话,我认为你真正需要的是一个函数,它接受一个 HBITMAP 并输出一个字节数组,另一个函数是你描述的那样。像这样:

    private static byte[] GetBitmapData(IntPtr hBitmap)
    {
        var source = Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, null);
        // You may use Bmp, Jpeg or other encoder of your choice
        var encoder = new PngBitmapEncoder();
        encoder.Frames.Add(BitmapFrame.Create(source));
        var stream = new MemoryStream();
        encoder.Save(stream);
        return stream.ToArray();
    }

    private static BitmapSource GetBitmapSource(byte[] data)
    {
        return BitmapFrame.Create(new MemoryStream(data));
    }