0

I have to get a BitmapSource from an Image and for this I use an extension method like this:

public static BitmapSource ToBitmapSource(this Image image)
{
    LogEx.FunctionEnter();

    if (image == null)
        throw new ArgumentNullException("image");

    BitmapImage bitmapImage = null;
    using (MemoryStream memory = new MemoryStream())
    {
        image.Save(memory, ImageFormat.Jpeg);
        memory.Position = 0;
        bitmapImage = new BitmapImage();
        bitmapImage.BeginInit();
        bitmapImage.StreamSource = memory;
        bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
        bitmapImage.EndInit();
    }

    LogEx.FunctionLeave("Rendered image as BitmapSource");
    return bitmapImage;
}

If I now release the handle of this and dispose the original Image it stays in the memory, even after calling the GC by hand multiple times. I tested to use files instead of streams with this little piece of code:

string filename = $"c:\\temp\\{page}.jpg";
if (File.Exists(filename))
{
    File.Delete(filename);
}
_highResPageImages[page].Save(filename, ImageFormat.Jpeg);
Uri uri = new Uri(filename);
BitmapImage source = new BitmapImage();
source.BeginInit();
source.UriSource = uri;
source.CacheOption = BitmapCacheOption.OnLoad;
source.EndInit();
Document.PageImage = source;
// Ducument.PageImage = _highResPageImages[page].ToBitmapSource();

And even if there is also an OnLoad, it gets disposed when the handle is released. So it must be something with the MemoryStream. But what? I tried this WrapperStream I found somewhere else and to use the Freeze() Method of BitmapImage, but both to no a avail. The problem is, that I cannot cache the images on the drive of the customer (even if it wouldn't cost a huge amount of time to do so) and I get the Image from another DLL, so I cannot change it beforehand. Has someone else an idea?

Edit: I use WPF and the value of the handle is being used in a Binding for display. Maybe that matters somehow. Or that I use BitmapSource in the Binding and handle and not the original BitmapImage.

Marcel Grüger
  • 885
  • 1
  • 9
  • 25
  • How do you know it stays in memory? – JHBonarius Apr 21 '21 at 06:09
  • I have two such methods. One for highres images and one for lowres thumbnails. They run parallel and I analyze it afterwards with VS and that said before I changed it, that I used nearly 65MB for 3 pages in highres and 19 MB in lowres and after my change it said 0 MB for highres and 19 MB for lowres. I analyzed it nearly the complete day before I posted here. Believe me ;) – Marcel Grüger Apr 21 '21 at 06:13
  • The whole point of `BitmapCacheOption.OnLoad` is that it's cached in memory. So I don't fully understand. – JHBonarius Apr 21 '21 at 06:15
  • Right, but when you release the handle (BitmapImage myImage = null;) the GC collects the BitmapImage even with OnLoad, dispose it and release the memory. But apperently it doesn't when you use a MemoryStream instead of a file handle. – Marcel Grüger Apr 21 '21 at 06:21
  • 1
    Thanks, I get it now. Sounds like a bug. But I don't know if they will fix it, because this is pretty old stuff. Cannot look into this in detail at this at the moment. Maybe you can find some static property in [the sources](https://github.com/dotnet/wpf/blob/main/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/Imaging/Generated/BitmapImage.cs). Edit: however, it can also be that a second reference/ copy of the image is made, with will block the gc from cleaning up. – JHBonarius Apr 21 '21 at 07:41
  • P.s. is it possible to call .Dispose? – JHBonarius Apr 21 '21 at 08:07
  • BitmapSource (or BitmapImage) do not include a disposing method. That would be too easy. ;) Normally they are managed by the GC and work fine. Except with a MemoryStream. – Marcel Grüger Apr 21 '21 at 08:11

1 Answers1

1

Try this in ToBitmapSource:

...
bitmapImage.StreamSource = null;
return bitmapImage