我需要通过像素绘制一个图像像素并显示它一个WPF内部。 我试图通过使用要做到这一点System.Drawing.Bitmap
然后用CreateBitmapSourceFromHBitmap()
创建BitmapSource
的WPF Image控件。 我有内存泄漏的地方,因为当CreateBitmapSourceFromBitmap()
反复调用的内存使用量上升,直到应用程序结束不断下降。 如果我不叫CreateBitmapSourceFromBitmap()
有内存使用情况无明显变化。
for (int i = 0; i < 100; i++)
{
var bmp = new System.Drawing.Bitmap(1000, 1000);
var source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
bmp.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty,
System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
source = null;
bmp.Dispose();
bmp = null;
}
我能做些什么来释放BitmapSource
内存?
Answer 1:
MSDN的Bitmap.GetHbitmap()
规定:
备注
你是负责调用GDI DeleteObject的方法来释放由GDI位图对象使用的内存。
因此,使用下面的代码:
// at class level
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
// your code
using (System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(1000, 1000))
{
IntPtr hBitmap = bmp.GetHbitmap();
try
{
var source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
}
finally
{
DeleteObject(hBitmap);
}
}
我也取代您Dispose()
通过调用using
的语句。
Answer 2:
每当与非托管手柄处理它可以使用“安全把手”包装是个好主意:
public class SafeHBitmapHandle : SafeHandleZeroOrMinusOneIsInvalid
{
[SecurityCritical]
public SafeHBitmapHandle(IntPtr preexistingHandle, bool ownsHandle)
: base(ownsHandle)
{
SetHandle(preexistingHandle);
}
protected override bool ReleaseHandle()
{
return GdiNative.DeleteObject(handle) > 0;
}
}
所以只要你表面的手柄构建一个像(理想情况下你的API绝不会暴露IntPtr的,他们总是返回安全把手):
IntPtr hbitmap = bitmap.GetHbitmap();
var handle = new SafeHBitmapHandle(hbitmap , true);
并使用它像这样:
using (handle)
{
... Imaging.CreateBitmapSourceFromHBitmap(handle.DangerousGetHandle(), ...)
}
该基地的SafeHandle为您提供了一个自动一次性/终结模式,所有你需要做的是重写ReleaseHandle方法。
Answer 3:
我有同样的要求和问题(内存泄漏)。 我实现了相同的解决方案标记为答案。 但是,尽管该解决方案的工作原理,它造成不可接受的命中性能。 运行在一个i7处理器,我的测试应用程序看到一个稳定的30-40%的CPU,RAM 200-400MB增加,垃圾收集器正在运行几乎每毫秒。
自从我做视频处理,我需要更好的性能。 我想出了下面,所以以为我也有同感。
可重复使用的全局对象
//set up your Bitmap and WritableBitmap as you see fit
Bitmap colorBitmap = new Bitmap(..);
WriteableBitmap colorWB = new WriteableBitmap(..);
//choose appropriate bytes as per your pixel format, I'll cheat here an just pick 4
int bytesPerPixel = 4;
//rectangles will be used to identify what bits change
Rectangle colorBitmapRectangle = new Rectangle(0, 0, colorBitmap.Width, colorBitmap.Height);
Int32Rect colorBitmapInt32Rect = new Int32Rect(0, 0, colorWB.PixelWidth, colorWB.PixelHeight);
转换码
private void ConvertBitmapToWritableBitmap()
{
BitmapData data = colorBitmap.LockBits(colorBitmapRectangle, ImageLockMode.WriteOnly, colorBitmap.PixelFormat);
colorWB.WritePixels(colorBitmapInt32Rect, data.Scan0, data.Width * data.Height * bytesPerPixel, data.Stride);
colorBitmap.UnlockBits(data);
}
实现示例
//do stuff to your bitmap
ConvertBitmapToWritableBitmap();
Image.Source = colorWB;
其结果是稳定的10-13%的CPU,RAM 70-150MB,和垃圾收集器仅在运行6分钟运行两次。
Answer 4:
这是一个伟大的(!!)后,虽然所有的意见和建议,我花了一个小时才弄清楚件。 因此,这里是一个电话与SafeHandles得到的BitmapSource,然后它的一个例子使用,以创建一个PNG图片文件。 在最底层是“usings”和一些参考。 当然,没有信用的是我的,我只是抄写员。
private static BitmapSource CopyScreen()
{
var left = Screen.AllScreens.Min(screen => screen.Bounds.X);
var top = Screen.AllScreens.Min(screen => screen.Bounds.Y);
var right = Screen.AllScreens.Max(screen => screen.Bounds.X + screen.Bounds.Width);
var bottom = Screen.AllScreens.Max(screen => screen.Bounds.Y + screen.Bounds.Height);
var width = right - left;
var height = bottom - top;
using (var screenBmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
{
BitmapSource bms = null;
using (var bmpGraphics = Graphics.FromImage(screenBmp))
{
IntPtr hBitmap = new IntPtr();
var handleBitmap = new SafeHBitmapHandle(hBitmap, true);
try
{
bmpGraphics.CopyFromScreen(left, top, 0, 0, new System.Drawing.Size(width, height));
hBitmap = screenBmp.GetHbitmap();
using (handleBitmap)
{
bms = Imaging.CreateBitmapSourceFromHBitmap(
hBitmap,
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
} // using
return bms;
}
catch (Exception ex)
{
throw new ApplicationException($"Cannot CopyFromScreen. Err={ex}");
}
} // using bmpGraphics
} // using screen bitmap
} // method CopyScreen
下面是使用,也是“安全处理”类:
private void buttonTestScreenCapture_Click(object sender, EventArgs e)
{
try
{
BitmapSource bms = CopyScreen();
BitmapFrame bmf = BitmapFrame.Create(bms);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(bmf);
string filepath = @"e:\(test)\test.png";
using (Stream stm = File.Create(filepath))
{
encoder.Save(stm);
}
}
catch (Exception ex)
{
MessageBox.Show($"Err={ex}");
}
}
public class SafeHBitmapHandle : SafeHandleZeroOrMinusOneIsInvalid
{
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern int DeleteObject(IntPtr hObject);
[SecurityCritical]
public SafeHBitmapHandle(IntPtr preexistingHandle, bool ownsHandle)
: base(ownsHandle)
{
SetHandle(preexistingHandle);
}
protected override bool ReleaseHandle()
{
return DeleteObject(handle) > 0;
}
}
最后来看看我的“usings”:
using System;
using System.Linq;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Media.Imaging;
using System.Windows.Interop;
using System.Windows;
using System.IO;
using Microsoft.Win32.SafeHandles;
using System.Security;
引用的DLL文件包括:* PresentationCore * System.Core程序* System.Deployment * System.Drawing中* WindowsBase
Answer 5:
我对别人的解决方案谁想要从内存或其他类加载图像
public static InteropBitmap Bitmap2BitmapImage(Bitmap bitmap)
{
try
{
var source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(bitmap.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
return (InteropBitmap)source;
}
catch (Exception e)
{
MessageBox.Show("Convertion exception: " + e.Message + "\n" +e.StackTrace);
return null;
}
}
然后,我用它设置图像的来源
CurrentImage.Source = ImageConverter.Bitmap2BitmapImage(cam.Bitmap);
图像是下述定义
<Image x:Name="CurrentImage" Margin="5" StretchDirection="Both"
Width="{Binding Width}"
Height="{Binding Height}">
</Image>
文章来源: WPF CreateBitmapSourceFromHBitmap() memory leak