2009-10-09 57 views
41

我需要逐個像素繪製圖像並將其顯示在WPF中。我試圖通過使用System.Drawing.Bitmap,然後使用CreateBitmapSourceFromHBitmap()爲WPF圖像控件創建BitmapSource來做到這一點。我有一個內存泄漏的地方,因爲當重複調用CreateBitmapSourceFromBitmap()時,內存使用量會增加,直到應用程序結束纔會停止。如果我不打電話CreateBitmapSourceFromBitmap()內存使用情況沒有明顯變化。WPF CreateBitmapSourceFromHBitmap()內存泄漏

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內存?

回答

67

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); 
    } 
} 

我也由一個using語句替換您的通話Dispose()

+0

工作。測試結束後會留下一些剩餘內存,但垃圾收集器會將其記錄下來。謝謝Julien。 – 2009-10-09 22:00:42

+0

太棒了。卡在第三方圖書館和困難的地方之間。這寫出來了。 – 2012-11-21 11:23:39

+0

以下是MSDN文章Bitmap.GetHBitmap的鏈接,其中@JulienLebosquain引用自http://msdn.microsoft.com/en-us/library/1dz311e4.aspx – Zack 2013-06-18 15:28:24

20

每當與非託管處理處理它可以是一個好主意,用「安全處理」包裝:

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方法即可。

+0

這是一個很好的提示 – JohannesH 2012-05-18 21:43:58

+0

非常好的迷你文章關於我應該更好地瞭解的東西。 – Cameron 2015-08-02 20:41:32

+1

「答案」指向正確的方向,但仍然沒有奏效 - 我仍然記憶猶新 - 但你的解決方案完美無缺 - 不僅如此,而且我喜歡以這種方式包裝 - 這是真正的抽象和編碼的未來 - 對不起,我被帶走了 – 2016-12-25 21:22:35

5

我有相同的要求和問題(內存泄漏)。我實現了與標記爲答案相同的解決方案。但是,儘管解決方案有效,但它對性能造成了令人難以接受的衝擊。運行在i7上,我的測試應用程序看到一個穩定的30-40%的CPU,200-400MB的RAM增加,垃圾收集器運行幾乎每毫秒。

由於我在做視頻處理,我需要更好的性能。我想出了以下內容,所以我想分享一下。

可重複使用的全局對象

//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,70-150MB RAM,垃圾收集器只在6分鐘內運行兩次。

+0

我已經遇到了同樣的問題。我試圖應用你的解決方案,但是我在** WritePixels **函數(** HRESULT:0x88982f0D ** - > ** WINCODEC_ERR_ALREADYLOCKED **)上得到了** COMException **。你有什麼主意嗎?謝謝 – 2017-08-14 09:25:27

+1

不,對不起,我無法重新報錯。根據你的錯誤,我想你試圖直接訪問位圖。看看,發生了什麼事是你從Kinect流中複製位圖,並將其寫入到你自己的WritableBitmap中,所有這些都在轉換代碼中。嘗試雙重檢查鎖定和解鎖順序,即在位圖 - >位圖數據 - > WritableBitmap之間移動,並確定矩形的大小正確,包括z軸= bytesPerPixel。祝你好運 – TrickySituation 2017-08-18 00:40:22

+0

謝謝你的建議。重新檢查源代碼後,我發現問題是因爲我沒有直接使用WriteableBitmap。轉換後,我創建一個基於WriteableBitmap的TransformedBitmap,然後如果我修改WriteableBitmap,則可能會發生異常。你知道原因嗎? – 2017-08-18 03:34:46

0

這是一篇很棒的貼子,雖然有了所有的意見和建議,但花了我一個小時才弄清楚這些作品。所以這裏是一個調用來獲得帶有SafeHandles的BitMapSource,然後用它來創建一個.PNG圖像文件的例子。最底層是「使用」和一些參考文獻。當然,沒有一個信用是我的,我只是抄寫員。

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 * Windo wsBase