2

I have a WPF application where I am saving couple of hundreds of BitmapSources by converting them to TIFF images using TiffBitmapEncoder. However, I have this strange memory consumption which is often throwing Insufficient Memory Exception.

NOTE:

  • I have 8GB of ram installed.
  • The image sizes vary from 10x10 to 300x300 pixels (quite small)

Here is the code which works:

static void SaveBitmapSource(BitmapSource bitmapSource)
{
    TiffBitmapEncoder encoder = new TiffBitmapEncoder();
    encoder.Compression = TiffCompressOption.Zip;
    BitmapFrame frame = BitmapFrame.Create(bitmapSource);

    encoder.Frames.Add(frame);

    using (MemoryStream ms = new MemoryStream())
    {
        encoder.Save(ms);
    }
}

And here is the screen shot of my memory:

No memory exception

Now if I clone the BitmapSource (even just once) then I get this huge memory allocation causing the Insufficient Memory Exception.

static BitmapSource source2 = null;
static void SaveBitmapSource(BitmapSource bitmapSource)
{
    if (source2 == null)
    {
        source2 = bitmapSource.Clone();
    }
    TiffBitmapEncoder encoder = new TiffBitmapEncoder();
    encoder.Compression = TiffCompressOption.Zip;
    BitmapFrame frame = BitmapFrame.Create(source2);

    encoder.Frames.Add(frame);

    using (MemoryStream ms = new MemoryStream())
    {
        encoder.Save(ms);
    }
}

Here is the screenshot of my memory for the second code sample

Memory exception

Does anyone know what could be causing this and how to fix it?

The difference is that the BitmapSource in the first example has been rendered to the screen and the second one hasn't. My suspicions are this could be something to do with GPU and the Dispatcher that might be hardware accelerating the conversion whereas the second one is done on CPU where there is some sort of a bug...

Tried:

  • Tried calling GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true); after the SaveBitmapSource() with no luck
Miro Bucko
  • 1,123
  • 1
  • 13
  • 26
  • Is there anything disposing of source2? – BlueMonkMN Feb 24 '14 at 16:36
  • BitmapSource does not implement IDisposable – Miro Bucko Feb 24 '14 at 16:38
  • Curious if adding `GC.Collect()` to the end of your function would affect the memory usage. Not as a solution, but as a diagnostic step. – BlueMonkMN Feb 24 '14 at 16:49
  • I have tried GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true); but didnt make any difference – Miro Bucko Feb 24 '14 at 16:59
  • But why do you need to call `BitmapSource.Clone` at all? There seems to be no reason to do so. – Clemens Feb 24 '14 at 17:21
  • I use BitmapSource.Clone because it produces the same effect as my code which basically creates a new BitmapSource from the old one. – Miro Bucko Feb 24 '14 at 18:52
  • 1
    It is a spike, not a step. So of course this is not a memory leak. Doesn't have anything to do with the GC either, this is all unmanaged code. The traditional mistake that prevents you from using all that wonderful hardware you have is to force your program to run in 32-bit mode. Which you fix with Project + Properties, Build tab, Platform target setting. – Hans Passant Feb 25 '14 at 16:11

3 Answers3

2

you have to use filestream to reduce to memory usage;

        BitmapDecoder decoder;
        using (Stream appendToOutput = File.Open(files[0], FileMode.Open, FileAccess.Read, FileShare.Read))
        {
            decoder = BitmapDecoder.Create(appendToOutput, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None);
            using (Stream output = File.Open(outputFile, FileMode.Create, FileAccess.Write))
            {
                TiffBitmapEncoder childEncoder = new TiffBitmapEncoder();
                if(Path.GetExtension(files[0]).Replace(".", "") == ScanningImageFormat.Jpeg) {
                    childEncoder.Compression = TiffCompressOption.Zip;
                } else {
                    childEncoder.Compression = TiffCompressOption.Ccitt4;
                }

                foreach (BitmapFrame frm in decoder.Frames)
                {
                    childEncoder.Frames.Add(frm);
                }

                List<Stream> imageStreams = new List<Stream>();
                try
                {
                    for (int i = 1; i < files.Count; i++)
                    {
                        string sFile = files[i];
                        BitmapFrame bmp = null;
                        Stream original = File.Open(sFile, FileMode.Open, FileAccess.Read);
                        imageStreams.Add(original);
                        bmp = BitmapFrame.Create(original, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None);
                        childEncoder.Frames.Add(bmp);
                    }
                    childEncoder.Save(output);
                }
                finally
                {
                    try
                    {
                        foreach (Stream s in imageStreams)
                        {
                            s.Close();
                        }
                    }
                    catch { }
                }
            }
        }
        decoder = null;
serdartu
  • 41
  • 5
1

I have resolved the problem by calling

GC.WaitForPendingFinalizers();

right after SaveBitmapSource().

So my guess is that there is some unmanaged resource inside BitmapSource and/or BitmapEncoder which was not being released untill the Finalize methods were run...

Miro Bucko
  • 1,123
  • 1
  • 13
  • 26
0

I'm glad that resolves the issue. But I'm not convinced that's the right fix. I see that you're invoking BitmapFrame.Create() with just one parameter. You might want to look at that more carefully..

Try using the BitmapCacheOption.None flag - by default it may be caching each of the bitmaps in memory for no reason:

BitmapFrame.Create(source, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None);

Ketchup201
  • 129
  • 1
  • 9