将BitmapImage转换为Bitmap和将Bitmap转换为BitmapImage

73

我在C#中有一个BitmapImage。我需要对图像进行操作,比如灰度化、在图像上添加文本等。

我在stackoverflow上找到了一个接受Bitmap并返回Bitmap的函数用于灰度化。

因此我需要将BitmapImage转换为Bitmap,进行操作,然后再转换回来。

我该如何做?这是最佳方法吗?


如果您不想在内存中创建副本,那么 sharedbitmapsource 是您所需要的。stackoverflow.com/a/32841840/690656 - Andreas
8个回答

107

不需要使用外部库。

将 BitmapImage 转换为 Bitmap:

private Bitmap BitmapImage2Bitmap(BitmapImage bitmapImage)
{
    // BitmapImage bitmapImage = new BitmapImage(new Uri("../Images/test.png", UriKind.Relative));

    using(MemoryStream outStream = new MemoryStream())
    {
        BitmapEncoder enc = new BmpBitmapEncoder();
        enc.Frames.Add(BitmapFrame.Create(bitmapImage));
        enc.Save(outStream);
        System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(outStream);

        return new Bitmap(bitmap);
    }
}
将Bitmap转换回BitmapImage:
```

将Bitmap转换回BitmapImage:

```
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);

private BitmapImage Bitmap2BitmapImage(Bitmap bitmap)
{
    IntPtr hBitmap = bitmap.GetHbitmap();
    BitmapImage retval;

    try
    {
        retval = (BitmapImage)Imaging.CreateBitmapSourceFromHBitmap(
                     hBitmap,
                     IntPtr.Zero,
                     Int32Rect.Empty,
                     BitmapSizeOptions.FromEmptyOptions());
    }
    finally
    {
        DeleteObject(hBitmap);
    }

    return retval;
}

3
Bitmap2BitmapImage 的更简洁实现方式是:使用 Imaging.CreateBitmapSourceFromHBitmap 方法,将位图的 GetHbitmap() 返回值作为参数传入,同时传入 IntPtr.Zero、Int32Rect.Empty 和 BitmapSizeOptions.FromEmptyOptions() 三个参数。 - Steven
1
@BVB:确实需要释放句柄。我已经修复了它。这是我凭记忆做的,所以我不能保证什么 :) - Sascha Hennig
1
@Jeff:没有任何问题可以处理。该注释实际上是对被注释掉的“return bitmap;”进行评论。另外,为什么它不起作用?据我所知,MemoryStream实现了IDisposable接口,因此应该被处理。 - Sascha Hennig
4
BitmapImage2Bitmap函数中,为什么最后一行要写成return new Bitmap(bitmap)而不是简单地写成return bitmap - arnobpl
5
Bitmap2BitmapImage函数对我不起作用,我收到了System.InvalidCastException错误,无法将类型为'System.Windows.Interop.InteropBitmap'的对象转换为类型'System.Windows.Media.Imaging.BitmapImage'。 - n.jmurov
显示剩余13条评论

60

这是一个将 Bitmap 转换为 BitmapImage 的扩展方法。

    public static BitmapImage ToBitmapImage(this Bitmap bitmap)
    {
        using (var memory = new MemoryStream())
        {
            bitmap.Save(memory, ImageFormat.Png);
            memory.Position = 0;

            var bitmapImage = new BitmapImage();
            bitmapImage.BeginInit();
            bitmapImage.StreamSource = memory;
            bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
            bitmapImage.EndInit();
            bitmapImage.Freeze();

            return bitmapImage;
        }
    }

我更喜欢这个,因为它不涉及PInvoke...太棒了。 - Ignacio Soler Garcia
5
bitmapImage.EndInit();之后,调用bitmapImage.Freeze()可以避免在与GUI不同的线程上执行此操作时出现错误。 - Zachary Canann
2
MemoryStream版本比Imaging.CreateBitmapSourceFromHBitmap版本慢10倍。 - informatorius

9

如果你只需要从BitmapImage转换为Bitmap,那么这很容易实现。

private Bitmap BitmapImage2Bitmap(BitmapImage bitmapImage)
    {
        return new Bitmap(bitmapImage.StreamSource);
    }

2
也许不是这样吧?如果图像具有Uri源,则StreamSource肯定为null? - david.pfx
StreamSource 可能为 null。 - Vincent

5

使用System.Windows.Interop;

 private BitmapImage Bitmap2BitmapImage(Bitmap bitmap)
        {                
            BitmapSource i = Imaging.CreateBitmapSourceFromHBitmap(
                           bitmap.GetHbitmap(),
                           IntPtr.Zero,
                           Int32Rect.Empty,
                           BitmapSizeOptions.FromEmptyOptions());
            return (BitmapImage)i;
        }

7
也许我错过了什么,但是根据弹出的 InvalidCastException,你不能将 "System.Windows.Interop.InteropBitmap" 强制转换为 "System.Windows.Media.Imaging.BitmapImage"。 - WiiMaxx
3
它确实会抛出异常,但如果您想将 System.Drawing.Image 转换为能够在 WPF Image 控件中显示的图像,则可以返回 BitmapSource 而不是 BitmapImage,并删除强制转换。然后它就能完美工作了。 - MasterMastic
1
不要返回图像,只需返回BitmapSource。 - DotNetRussell
这个CreateBitmapSourceFromHBitmap版本与MemoryStream版本相比,速度快了10倍。 - informatorius
虽然这个方法可以工作,但是它存在内存泄露问题。请使用上面列出的"Sascha Hennig" 的解决方案,因为他们可以正确地处理非托管内存。 - BlueFuzzyThing

3

我刚刚尝试在我的代码中使用上述功能,我认为Bitmap2BitmapImage函数存在问题(可能还有其他函数)。

using (MemoryStream ms = new MemoryStream())

上述代码会导致流被处理掉,这意味着返回的BitmapImage将失去其内容。

由于我是WPF新手,不确定这是否是正确的技术解释,但在我的应用程序中,直到我删除了using指令,代码才能正常工作。


使用 "using" 语句块确实会释放对象。这就是 using 语句块的作用。一旦您不再需要流,请确实将其处理掉。因此,我在2年前发布的帖子中进行了编辑。BitmapImage2Bitmap-Fix 在关闭流之前创建了一个 Bitmap() 类型的新对象。对于 Bitmap2BitmapImage(),您要尝试根据 Steven 发布的答案评论所示的实现。基本上,您要做的是从流数据创建一个新对象,关闭流并返回创建的对象。 - Sascha Hennig

2

这里是异步版本。

public static Task<BitmapSource> ToBitmapSourceAsync(this Bitmap bitmap)
{
    return Task.Run(() =>
    {
        using (System.IO.MemoryStream memory = new System.IO.MemoryStream())
        {
            bitmap.Save(memory, ImageFormat.Png);
            memory.Position = 0;
            BitmapImage bitmapImage = new BitmapImage();
            bitmapImage.BeginInit();
            bitmapImage.StreamSource = memory;
            bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
            bitmapImage.EndInit();
            bitmapImage.Freeze();
            return bitmapImage as BitmapSource;
        }
    });

}

这是一个很好的解决方案,对我的情况非常适用。 - jv_

1
感谢Guillermo Hernandez,我创建了一个可行的代码变体。我在此代码中添加了命名空间以供参考。
System.Reflection.Assembly theAsm = Assembly.LoadFrom("My.dll");
// Get a stream to the embedded resource
System.IO.Stream stream = theAsm.GetManifestResourceStream(@"picture.png");

// Here is the most important part:
System.Windows.Media.Imaging.BitmapImage bmi = new BitmapImage();
bmi.BeginInit();
bmi.StreamSource = stream;
bmi.EndInit();

0

这将 System.Drawing.Bitmap 转换成 BitmapImage:

MemoryStream ms = new MemoryStream();
YOURBITMAP.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
BitmapImage image = new BitmapImage();
image.BeginInit();
ms.Seek(0, SeekOrigin.Begin);
image.StreamSource = ms;
image.EndInit();

BMP格式无法正确处理透明度,PNG更好。 - david.pfx

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