2009-10-09 44 views
41

Tôi cần vẽ một pixel ảnh theo pixel và hiển thị nó bên trong một WPF. Tôi đang cố thực hiện việc này bằng cách sử dụng System.Drawing.Bitmap rồi sử dụng CreateBitmapSourceFromHBitmap() để tạo một BitmapSource để điều khiển hình ảnh WPF. Tôi bị rò rỉ bộ nhớ ở đâu đó bởi vì khi CreateBitmapSourceFromBitmap() được gọi nhiều lần, việc sử dụng bộ nhớ sẽ tăng lên và không giảm cho đến khi ứng dụng kết thúc. Nếu tôi không gọi CreateBitmapSourceFromBitmap() thì không có thay đổi đáng chú ý trong việc sử dụng bộ nhớ.WPF CreateBitmapSourceFromHBitmap() rò rỉ bộ nhớ

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

Tôi có thể làm gì để giải phóng bộ nhớ BitmapSource?

Trả lời

67

MSDN tuyên bố rằng đối với Bitmap.GetHbitmap(): Bạn có trách nhiệm gọi phương thức GDI DeleteObject để giải phóng bộ nhớ được sử dụng bởi đối tượng bitmap GDI. Vì vậy, sử dụng đoạn mã sau:

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

Tôi cũng thay Dispose() cuộc gọi của bạn bằng một tuyên bố using.

+0

Điều đó có hiệu quả. Có một chút bộ nhớ còn lại được tổ chức sau khi thử nghiệm, nhưng bộ thu gom rác sẽ nhặt nó lên. Cảm ơn Julien. –

+0

Tuyệt vời. Bị kẹt giữa thư viện của bên thứ 3 và một nơi khó khăn. Điều này đã bình chọn nó. –

+0

Đây là liên kết đến bài viết MSmap Bitmap.GetHBitmap nơi @JulienLebosquain đang trích dẫn từ http://msdn.microsoft.com/en-us/library/1dz311e4.aspx – Zack

20

Bất cứ khi nào đối phó với không được quản lý xử lý nó có thể là một ý tưởng tốt để sử dụng "xử lý an toàn" 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; 
    } 
} 

Xây dựng một như vậy ngay sau khi bạn bề mặt một tay cầm (lý tưởng API của bạn sẽ không bao giờ tiếp xúc với IntPtr , họ sẽ luôn luôn trở về xử lý an toàn):

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

Và sử dụng nó như vậy:

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

Cơ sở SafeHandle cung cấp cho bạn mẫu dùng một lần/hoàn thiện tự động, tất cả những gì bạn cần làm là ghi đè phương thức ReleaseHandle.

+0

Đó là một mẹo tốt – JohannesH

+0

Bài viết nhỏ rất hay về một điều tôi nên biết rõ hơn. – Cameron

+1

"câu trả lời" chỉ đúng hướng nhưng vẫn không hoạt động - tôi vẫn còn thiếu bộ nhớ - nhưng giải pháp của bạn hoạt động hoàn hảo - và không chỉ vậy mà tôi còn thích gói theo cách này - nó trừu tượng và tương lai mã hóa - xin lỗi đã mang đi –

5

Tôi có cùng yêu cầu và vấn đề (rò rỉ bộ nhớ). Tôi đã triển khai giải pháp tương tự như được đánh dấu là câu trả lời. Nhưng mặc dù giải pháp hoạt động, nó gây ra một hit không thể chấp nhận để thực hiện. Chạy trên i7, ứng dụng thử nghiệm của tôi đã thấy CPU 30-40% ổn định, tăng 200-400MB RAM và bộ thu gom rác đang chạy gần như mỗi mili giây.

Vì tôi đang xử lý video, tôi cần hiệu suất tốt hơn nhiều. Tôi nghĩ ra những điều sau đây, nên tôi nghĩ tôi sẽ chia sẻ.

Reusable toàn cầu 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); 

Mã chuyển đổi

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

thực hiện Ví dụ

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

Kết quả là CPU 10-13% ổn định, RAM 70-150MB và bộ thu gom rác chỉ chạy hai lần trong 6 phút.

+0

Tôi đã gặp phải vấn đề tương tự. Tôi đã cố gắng áp dụng giải pháp của bạn nhưng tôi nhận được ** COMException ** trên ** WritePixels ** (** HRESULT: 0x88982f0D ** -> ** WINCODEC_ERR_ALREADYLOCKED **). Bạn còn ý kiến ​​nào không? Cảm ơn –

+1

Không, xin lỗi, tôi không thể sửa lỗi của bạn. Dựa trên lỗi tho của bạn, tôi nghĩ bạn đang cố gắng truy cập trực tiếp vào Bitmap. Hãy xem, những gì đang xảy ra là bạn sao chép bitmap từ luồng Kinect và ghi nó vào WritableBitmap của riêng bạn tất cả trong mã chuyển đổi. Hãy thử kiểm tra lại chuỗi khóa và mở khóa, khi bạn di chuyển giữa Bitmap -> BitmapData -> WritableBitmap và hình chữ nhật đó là kích thước thích hợp bao gồm z-axis = bytesPerPixel. Chúc may mắn – TrickySituation

+0

Cảm ơn bạn đã đề xuất. Sau khi kiểm tra lại mã nguồn, tôi thấy rằng vấn đề là vì tôi không sử dụng WriteableBitmap trực tiếp. Sau khi chuyển đổi, tôi tạo một TransformedBitmap dựa trên WriteableBitmap, và sau đó nếu tôi sửa đổi WriteableBitmap, ngoại lệ có thể xảy ra. Bạn có biết lý do không? –

0

Đây là một (!!) lớn bưu điện, mặc dù với tất cả các ý kiến ​​và đề nghị, nó đã cho tôi một giờ để tìm ra từng mảnh. Vì vậy, đây là một cuộc gọi để có được BitMapSource với SafeHandles, và sau đó một ví dụ sử dụng của nó để tạo ra một tập tin hình ảnh .PNG. Ở dưới cùng là 'sử dụng' và một số tài liệu tham khảo.Tất nhiên, không có tín dụng nào là của tôi, tôi chỉ là người ghi chép.

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 

Dưới đây là cách sử dụng, và cũng là lớp "Xử lý an toàn":

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

Và cuối cùng là một cái nhìn 'usings' của tôi:

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; 

Các DLL tham chiếu bao gồm: * PresentationCore * System.Core * System.Deployment * System.Drawing * Windo wsBase