可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I need to draw an image pixel by pixel and display it inside a WPF. I am attempting to do this by using a System.Drawing.Bitmap
then using CreateBitmapSourceFromHBitmap()
to create a BitmapSource
for a WPF Image control. I have a memory leak somewhere because when the CreateBitmapSourceFromBitmap()
is called repeatedly the memory usage goes up and does not drop off until the application is ended. If I don\'t call CreateBitmapSourceFromBitmap()
there is no noticeable change in memory usage.
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;
}
What can I do to free the BitmapSource
memory?
回答1:
MSDN for Bitmap.GetHbitmap()
states:
Remarks
You are responsible for calling the GDI DeleteObject method to free the memory used by the GDI bitmap object.
So use the following code:
// 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);
}
}
I also replaced your Dispose()
call by an using
statement.
回答2:
Whenever dealing with unmanaged handles it can be a good idea to use the \"safe handle\" wrappers:
public class SafeHBitmapHandle : SafeHandleZeroOrMinusOneIsInvalid
{
[SecurityCritical]
public SafeHBitmapHandle(IntPtr preexistingHandle, bool ownsHandle)
: base(ownsHandle)
{
SetHandle(preexistingHandle);
}
protected override bool ReleaseHandle()
{
return GdiNative.DeleteObject(handle) > 0;
}
}
Construct one like so as soon as you surface a handle (ideally your APIs would never expose IntPtr, they would always return safe handles):
IntPtr hbitmap = bitmap.GetHbitmap();
var handle = new SafeHBitmapHandle(hbitmap , true);
And use it like so:
using (handle)
{
... Imaging.CreateBitmapSourceFromHBitmap(handle.DangerousGetHandle(), ...)
}
The SafeHandle base gives you an automatic disposable/finalizer pattern, all you need to do is override the ReleaseHandle method.
回答3:
I had the same requirement and issue (memory leak). I implemented the same solution as marked as answer. But although the solution works, it caused an unacceptable hit to performance. Running on a i7, my test app saw a steady 30-40% CPU, 200-400MB RAM increases and the garbage collector was running almost every millisecond.
Since I\'m doing video processing, I\'m in need of much better performance. I came up with the following, so thought I would share.
Reusable Global Objects
//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);
Conversion Code
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);
}
Implementation Example
//do stuff to your bitmap
ConvertBitmapToWritableBitmap();
Image.Source = colorWB;
The result is a steady 10-13% CPU, 70-150MB RAM, and the garbage collector only ran twice in a 6 minute run.
回答4:
This is a great(!!) post, although with all the comments and suggestions, it took me an hour to figure out the pieces. So here is a call to get the BitMapSource with SafeHandles, and then an example usage of it to create a .PNG image file. At the very bottom are the \'usings\' and some of the references. Of course, none of the credit is mine, I am just the scribe.
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
Here is the usage, and also the \"Safe Handle\" class:
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;
}
}
And finally a look at my \'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;
The DLLs referenced included:
* PresentationCore
* System.Core
* System.Deployment
* System.Drawing
* WindowsBase
回答5:
I have a solution for someone who want to load image from memory or other class
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;
}
}
And then I use it the set the source of an image
CurrentImage.Source = ImageConverter.Bitmap2BitmapImage(cam.Bitmap);
Image is the following definition
<Image x:Name=\"CurrentImage\" Margin=\"5\" StretchDirection=\"Both\"
Width=\"{Binding Width}\"
Height=\"{Binding Height}\">
</Image>