C# - 将WPF Image.source转换为System.Drawing.Bitmap

20
我发现很多人将 BitmapSource 转换成 Bitmap,但是如何将 ImageSource 转换成 Bitmap 呢?我正在制作一个图像处理程序,需要从 Image 元素中显示的图像中提取位图。有没有人知道怎么做?
编辑1:
这是将 BitmapImage 转换为 Bitmap 的函数。请记得在编译器首选项中设置“不安全”选项。
public static System.Drawing.Bitmap BitmapSourceToBitmap(BitmapSource srs)
{
    System.Drawing.Bitmap btm = null;

    int width = srs.PixelWidth;

    int height = srs.PixelHeight;

    int stride = width * ((srs.Format.BitsPerPixel + 7) / 8);

    byte[] bits = new byte[height * stride];

    srs.CopyPixels(bits, stride, 0);

    unsafe
    {
        fixed (byte* pB = bits)
        {
            IntPtr ptr = new IntPtr(pB);

            btm = new System.Drawing.Bitmap(width, height, stride, System.Drawing.Imaging.PixelFormat.Format1bppIndexed, ptr);
        }
    }
    return btm;
}

接下来需要获取一个BitmapImage

RenderTargetBitmap targetBitmap = new RenderTargetBitmap(
    (int)inkCanvas1.ActualWidth,
    (int)inkCanvas1.ActualHeight,
    96d, 96d,
    PixelFormats.Default);

targetBitmap.Render(inkCanvas1);

MemoryStream mse = new MemoryStream();
System.Windows.Media.Imaging.BmpBitmapEncoder mem = new BmpBitmapEncoder();
mem.Frames.Add(BitmapFrame.Create(targetBitmap));
mem.Save(mse);

mse.Position = 0;
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.StreamSource = mse;
bi.EndInit();

下一步是转换它:

Bitmap b = new Bitmap(BitmapSourceToBitmap(bi));

不行,这太糟糕了。 - Jerry Nixon
4个回答

25

实际上,您不需要使用不安全的代码。 CopyPixels 有一个重载可以接受一个 IntPtr 参数:

public static System.Drawing.Bitmap BitmapSourceToBitmap2(BitmapSource srs)
{
    int width = srs.PixelWidth;
    int height = srs.PixelHeight;
    int stride = width * ((srs.Format.BitsPerPixel + 7) / 8);
    IntPtr ptr = IntPtr.Zero;
    try
    {
        ptr = Marshal.AllocHGlobal(height * stride);
        srs.CopyPixels(new Int32Rect(0, 0, width, height), ptr, height * stride, stride);
        using (var btm = new System.Drawing.Bitmap(width, height, stride, System.Drawing.Imaging.PixelFormat.Format1bppIndexed, ptr))
        {
            // Clone the bitmap so that we can dispose it and
            // release the unmanaged memory at ptr
            return new System.Drawing.Bitmap(btm);
        }
    }
    finally
    {
        if (ptr != IntPtr.Zero)
            Marshal.FreeHGlobal(ptr);
    }
}

请注意,Bitmap 的这个构造函数在内部调用 GdipCreateBitmapFromScan0,该函数期望用户在不再需要时释放内存... - Cameron
@Cameron,说得好。我编辑了我的答案以考虑到这一点。 - Thomas Levesque
不幸的是,在 System.Drawing.Bitmap 被处理之前,内存无法被释放(我找不到任何文档来证明这一点,但在一个互操作性很强的应用程序中尝试后,我的图像只有在释放后才能正常显示,因此它似乎并没有在内部复制数据)。此外,GDI 文档似乎要求步幅必须是 4 的倍数;所以 (srs.Format.BitsPerPixel + 7) / 8 应该是 (srs.Format.BitsPerPixel + 31) / 32 * 8,我相信。 - Cameron
@Cameron - 在克隆之前尝试冻结位图。这应该会释放对内存的占用。 - Jesse Chisholm

3

那个例子对我有效:

    public static Bitmap ConvertToBitmap(BitmapSource bitmapSource)
    {
        var width = bitmapSource.PixelWidth;
        var height = bitmapSource.PixelHeight;
        var stride = width * ((bitmapSource.Format.BitsPerPixel + 7) / 8);
        var memoryBlockPointer = Marshal.AllocHGlobal(height * stride);
        bitmapSource.CopyPixels(new Int32Rect(0, 0, width, height), memoryBlockPointer, height * stride, stride);
        var bitmap = new Bitmap(width, height, stride, PixelFormat.Format32bppPArgb, memoryBlockPointer);
        return bitmap;
    }

2

您的ImageSource不是BitmapSource吗?如果您从文件中加载图像,则应该是。

回复您的评论:

听起来它们应该是BitmapSource,BitmapSource是ImageSource的子类型。将ImageSource强制转换为BitmapSource并遵循其中一个博客文章。


不,它说它们是ImageSources。我正在从转换自位图的BitmapIamges中加载它们。 - user646265
谢谢,我想出了一种我认为很好的方法。我已经将其作为对我的问题的编辑发布了。 - user646265
@SimonStenderBoisen - ImageSource 可能来自 XAML 文件而不是 Bitmap 文件。如果是这样,它的底层类可能是 DrawingBrush,需要进行转换才能获得实际的 Bitmap - Jesse Chisholm

1
你根本不需要一个名为 BitmapSourceToBitmap 的方法。只需在创建内存流后执行以下操作即可:
mem.Position = 0;  
Bitmap b = new Bitmap(mem);

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