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

时间:2015-05-06 05:32:31

标签: c# bitmap pinvoke

我有一个包含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也可以。

3 个答案:

答案 0 :(得分:1)

如果您只需要一个BitmapSource(或一个ImageSource),您可以使用GDI的CreateBitmapIndirect function和WPF互操作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);

答案 1 :(得分:1)

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

bitmap.WidthBytes * bitmap.Height

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

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

也许您需要知道像素数据区域的大小受“填充”的影响,位于每个Scanline末尾的一些额外字节添加到完整的Int32块以加速位图加载。

答案 2 :(得分:0)

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

  

如果hgdiobj是通过调用CreateDIBSection创建的位图的句柄,并且指定的缓冲区足够大,则GetObject函数返回DIBSECTION结构。此外,DIBSECTION中包含的BITMAP结构的bmBits成员将包含指向位图的位值的指针。如果hgdiobj是通过任何其他方式创建的位图的句柄,则GetObject仅返回位图的宽度,高度和颜色格式信息。您可以通过调用GetDIBits或GetBitmapBits函数来获取位图的位值。

所以让我们从头开始吧。从我看到的,你的代码开始于一个名为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));
    }