4

I have a fair few images that I'm loading into a ListBox in my WPF application. Originally I was using GDI to resize the images (the originals take up far too much memory). That was fine, except they were taking about 400ms per image. Not so fine. So in search of another solution I found a method that uses TransformedBitmap (which inherits from BitmapSource). That's great, I thought, I can use that. Except I'm now getting memory leaks somewhere...

I'm loading the images asynchronously using a BackgroundWorker like so:

BitmapSource bs = ImageUtils.ResizeBitmapSource(ImageUtils.GetImageSource(photo.FullName));
                //BitmapSource bs = ImageUtils.GetImageSource(photo.FullName);
                bs.Freeze();

                this.dispatcher.Invoke(new Action(() => { photo.Source = bs; }));

GetImageSource just gets the Bitmap from the path and then converts to BitmapSource.

Here's the code snippet for ResizeBitmapSource:

const int thumbnailSize = 200;
        int width;
        int height;

        if (bs.Width > bs.Height)
        {
            width = thumbnailSize;
            height = (int)(bs.Height * thumbnailSize / bs.Width);
        }
        else
        {
            height = thumbnailSize;
            width = (int)(bs.Width * thumbnailSize / bs.Height);
        }

        BitmapSource tbBitmap = new TransformedBitmap(bs,
                                                           new ScaleTransform(width / bs.Width,
                                                                              height / bs.Height, 0, 0));

        return tbBitmap;

That code is essentially the code from: http://rongchaua.net/blog/c-wpf-fast-image-resize/

Any ideas what could be causing the leak?

edit: Here's the code for GetImageSource, as requested

using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read))
            {
                using (var bmp = Image.FromStream(stream, false, false))
                {
                    // Use WPF to resize
                    var bitmapSource = ConvertBitmapToBitmapSource(bmp);
                    bitmapSource = ResizeBitmapSource(bitmapSource);
                    return bitmapSource;
                }
            }
Harry
  • 3,076
  • 4
  • 28
  • 44
  • How did you diagnose the leak? And why do you think its in this code and not in for instance GetImageSource() ? – H H Mar 11 '10 at 20:14
  • The BitmapSource I'm using in the WPF control is resized properly, but looking at the memory used by the process, it's using the same memory as if it was a full sized image. So somewhere along the line the full-sized BitmapSource isn't being deleted. edit: and because GetImageSource doesn't really change. If I choose to resize with GDI it's fine. But when I resize using this TransformedBitmap method, it doesn't work. – Harry Mar 11 '10 at 20:17
  • 1
    Could you try and call manual GC to see if memory is released? If not you most likely have a reference somewhere which is holding the memory. – Mikael Svenson Mar 11 '10 at 21:05
  • Can you please post your code for `ImageUtils.GetImageSource()`? I suspect that the leak is occuring in there. – Alastair Pitts Mar 11 '10 at 21:21
  • I've added the code to GetImageSource. The code for ConvertBitmapToBitmapSource() simply uses Imaging.CreateBitmapSourceFromHBBitmap(). I thought it could be from that but not only did I use this method in the original, but I delete the Hbitmap IntPtr as well... – Harry Mar 12 '10 at 01:33
  • Check http://stackoverflow.com/questions/1343374/reducing-memory-usage-of-net-applications for code to do manual GC and trim the workingset to see if this releases memory at all. – Mikael Svenson Mar 12 '10 at 07:42

2 Answers2

4

I think you misunderstood how the TransformedBitmap works. It holds onto a reference to the source bitmap, and transforms it in memory. Maybe you could encode the transformed bitmap into a memory stream, and read it right back out. I'm not sure how fast this would be, but you wouldn't then be holding on to the full sized bitmap.

I found this blog post that returned a WriteableBitmap with the TransformedBitmap as the source. The WriteableBitmap will copy the pixel data to a memory buffer in the initializer, so it doesn't actually hold on to a reference to the TransformedBitmap, or the full sized image.

Abe Heidebrecht
  • 30,090
  • 7
  • 62
  • 66
1

At a guess, from looking at your code you might need to dispose of the bitmap returned by the call to ImageUtils.GetImageSource(photo.FullName).

I have also noted on the blog you pointed out that the author has added an update (11th of March) about inserting a using statement to prevent memory leaks.

Andrew Bienert
  • 348
  • 2
  • 8
  • Yep, that was based on the comment that I added. It doesn't seem to work though. Not saying that his code is incorrect, but there's something in mine that's causing it to go.. awry... – Harry Mar 12 '10 at 01:29