2009-10-09 6 views
41

Мне нужно нарисовать пиксель за пикселем и отобразить его внутри WPF. Я пытаюсь сделать это, используя System.Drawing.Bitmap, затем используя CreateBitmapSourceFromHBitmap(), чтобы создать BitmapSource для управления изображением WPF. У меня есть утечка памяти где-то, потому что, когда вызывается 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 bitmap. Так что используйте следующий код:

// 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 заявления.

+0

Это работает. После теста осталось немного остаточной памяти, но сборщик мусора забирает его. Спасибо, Жюльен. –

+0

Фантастический. Застрял между сторонней библиотекой и жестким местом. Это усугубляло это. –

+0

Вот ссылка на статью MSDN Bitmap.GetHBitmap, где @JulienLebosquain цитирует с http://msdn.microsoft.com/en-us/library/1dz311e4.aspx – Zack

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

+0

Очень хорошая мини-статья о чем-то, что я должен знать лучше. – Cameron

+1

«Ответ» указывал на правильное направление, но все еще не работал - я все еще забыл - но ваше решение работает безупречно - и не только это, но и я люблю обертывание таким образом - это настоящая абстракция и будущее кодирования - извините, увлекшись –

5

У меня было такое же требование и проблема (утечка памяти). Я реализовал то же самое решение, что и как ответ. Но несмотря на то, что решение работает, это вызвало недопустимый удар производительности. Запустив i7, мое тестовое приложение увидело устойчивый 30-40% процессор, 200-400 МБ ОЗУ увеличилось, а сборщик мусора работал почти каждые миллисекунды.

Поскольку я занимаюсь обработкой видео, мне нужна намного лучшая производительность. Я придумал следующее, так что подумал, что поделюсь.

многоразовый глобальных объектов

//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% процессор, операционная память 70-150 МБ, а сборщик мусора работает только дважды в течение 6 минут.

+0

Я уже столкнулся с той же проблемой. Я попытался применить ваше решение, но у меня есть ** COMException ** в функции ** WritePixels ** (** HRESULT: 0x88982f0D ** -> ** WINCODEC_ERR_ALREADYLOCKED **). Есть ли у вас какие-либо идеи? Спасибо –

+1

Нет, извините, я не могу воспроизвести вашу ошибку. Основываясь на вашей ошибке, я думаю, что вы пытаетесь напрямую получить доступ к Bitmap. Смотрите, что происходит, это то, что вы копируете растровое изображение из потока Kinect и записываете его в свой собственный WritableBitmap в коде преобразования. Попробуйте дважды проверить последовательность блокировки и разблокировки, что перемещение между Bitmap -> BitmapData -> WritableBitmap и что прямоугольник является правильным размером, включая ось z = bytesPerPixel. Удачи – TrickySituation

+0

Спасибо за ваше предложение. После проверки исходного кода я обнаружил, что проблема в том, что я не использовал WriteableBitmap напрямую. После преобразования я создаю TransformedBitmap на основе WriteableBitmap, а затем, если я изменяю WriteableBitmap, исключение может произойти. Вы знаете причину? –

0

Это замечательный (!!) пост, хотя со всеми комментариями и предложениями мне потребовался час, чтобы разобраться в деталях. Итак, вот призыв получить BitMapSource с помощью SafeHandles, а затем использовать его пример для создания файла изображения .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

Смежные вопросы