2009-10-09 20 views
41

Necesito dibujar una imagen píxel por píxel y mostrarla dentro de un WPF. Estoy intentando hacer esto usando un System.Drawing.Bitmap y luego usando CreateBitmapSourceFromHBitmap() para crear un BitmapSource para un control de imagen WPF. Tengo una pérdida de memoria en alguna parte porque cuando se llama repetidamente al CreateBitmapSourceFromBitmap(), el uso de la memoria aumenta y no baja hasta que finaliza la aplicación. Si no llamo al CreateBitmapSourceFromBitmap(), no hay cambios notables en el uso de la memoria.WPF CreateBitmapSourceFromHBitmap() pérdida de memoria

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

¿Qué puedo hacer para liberar la memoria BitmapSource?

Respuesta

67

MSDN indica que para Bitmap.GetHbitmap(): Usted es responsable de llamar al método GDI DeleteObject para liberar la memoria utilizada por el objeto de mapa de bits de GDI. a fin de utilizar el siguiente código:

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

También reemplazado por su llamada Dispose() una declaración using.

+0

Eso funciona. Hay un poco de memoria residual después de la prueba, pero el recolector de basura la recoge. Gracias Julien. –

+0

Fantástico. Estaba atrapado entre una biblioteca de terceros y un lugar difícil. Esto lo mordió. –

+0

Aquí hay un enlace al artículo de MSDN Bitmap.GetHBitmap donde @JulienLebosquain está citando de http://msdn.microsoft.com/en-us/library/1dz311e4.aspx – Zack

20

Siempre que se trata de no administrado maneja puede ser una buena idea utilizar las envolturas "manija segura":

public class SafeHBitmapHandle : SafeHandleZeroOrMinusOneIsInvalid 
{ 
    [SecurityCritical] 
    public SafeHBitmapHandle(IntPtr preexistingHandle, bool ownsHandle) 
     : base(ownsHandle) 
    { 
     SetHandle(preexistingHandle); 
    } 

    protected override bool ReleaseHandle() 
    { 
     return GdiNative.DeleteObject(handle) > 0; 
    } 
} 

Construir una como tan pronto como sale a la superficie un mango (idealmente sus APIs nunca se expondrían IntPtr , siempre volverían manijas seguras):

IntPtr hbitmap = bitmap.GetHbitmap(); 
var handle = new SafeHBitmapHandle(hbitmap , true); 

y usarlo de esta manera:

using (handle) 
{ 
    ... Imaging.CreateBitmapSourceFromHBitmap(handle.DangerousGetHandle(), ...) 
} 

La base SafeHandle le ofrece un patrón automático desechable/finalizador, todo lo que necesita hacer es anular el método ReleaseHandle.

+0

Ese es un buen consejo – JohannesH

+0

Muy bonito mini artículo sobre algo que debería saber mejor. – Cameron

+1

la "respuesta" apuntó en la dirección correcta pero no funcionó (todavía me quedé sin memoria) pero tu solución funciona sin problemas, y no solo eso, sino que también me encanta envolver de esta manera: es la abstracción verdadera y el futuro de la codificación - lo siento se ha llevado a –

5

Tenía el mismo requisito y problema (pérdida de memoria). Implementé la misma solución marcada como respuesta. Pero aunque la solución funciona, causó un golpe inaceptable para el rendimiento. Corriendo en un i7, mi aplicación de prueba vio una constante de 30-40% de CPU, 200-400 MB de RAM aumenta y el recolector de basura funcionaba casi cada milisegundo.

Como estoy procesando video, necesito un rendimiento mucho mejor. Se me ocurrió lo siguiente, así que pensé que lo compartiría.

reutilizable objetos globales

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

Código Conversión

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

Ejemplo de implementación

//do stuff to your bitmap 
ConvertBitmapToWritableBitmap(); 
Image.Source = colorWB; 

El resultado es una CPU estable de 10-13%, RAM de 70-150 MB, y el recolector de basura solo se ejecutó dos veces en una ejecución de 6 minutos.

+0

Ya he encontrado el mismo problema. He intentado aplicar su solución, pero obtuve una ** COMException ** en la función ** WritePixels ** (** HRESULT: 0x88982f0D ** -> ** WINCODEC_ERR_ALREADYLOCKED **). ¿Tiene alguna idea? Gracias –

+1

No, lo siento, no puedo reproducir su error. En función de su error, creo que está intentando acceder al mapa de bits directamente. Mira, lo que sucede es que estás copiando el mapa de bits de la transmisión de Kinect y escribiéndolo en tu propio WritableBitmap todo en el código de conversión. Intente verificar dos veces la secuencia de bloqueo y desbloqueo, que se mueva entre Bitmap -> BitmapData -> WritableBitmap, y que el rectángulo tenga el tamaño adecuado, incluido el z-axis = bytesPerPixel. Buena suerte – TrickySituation

+0

Gracias por su sugerencia. Después de volver a verificar el código fuente, descubrí que el problema se debe a que no usé el WriteableBitmap directamente. Después de la conversión, creo un TransformedBitmap basado en el WriteableBitmap, y luego si modifico el WriteableBitmap, la excepción puede ocurrir. ¿Sabe usted la razón? –

0

Esta es una gran (!!) publicación, aunque con todos los comentarios y sugerencias, me llevó una hora descubrir las piezas. Así que aquí hay una llamada para obtener BitMapSource con SafeHandles, y luego un uso de ejemplo para crear un archivo de imagen .PNG. En la parte inferior están los 'usos' y algunas de las referencias.Por supuesto, ninguno de los méritos es mío, solo soy el escriba.

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 

Aquí es el uso, y también la clase "Manija segura":

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

Y por último un vistazo a mis '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; 

Las DLL que se hace referencia incluye: * PresentationCore * System.Core * System.Deployment * System.Drawing * Windo wsBase