TiffBitmapEncoder, memory bug causing Insufficient Memory Exception in C#/WPF

1.6k Views Asked by At

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
3

There are 3 best solutions below

0
On

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

0
On

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...

0
On

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;