0

I wrote a C# program that takes in a directory name, iterates through the files in it in batches of 100, and outputs gifs.

class Program
{
    static void Main(string[] args)
    {
        GifBitmapEncoder encoder = new GifBitmapEncoder();

        int i = 0;
        int filei = 0;
        foreach (string fileName in Directory.GetFiles(args[0]).OrderBy(s => s))
        {
            i++;
            AddFileToBitmap(encoder, fileName);
            Console.WriteLine("Added file {0:000}: {1}", i, fileName);

            if (i % 100 == 0)
            {
                string outFile = string.Format("output{0}.gif", filei);
                Console.WriteLine("Saving to file: {0}", outFile);
                SaveGif(encoder, outFile);

                filei++;
                encoder.Frames.Clear();
                encoder = new GifBitmapEncoder();
                GC.Collect();
                Console.WriteLine();
            }
        }
    }

    static void AddFileToBitmap(GifBitmapEncoder encoder, string fileName)
    {
        using (Bitmap img = (Bitmap)Bitmap.FromFile(fileName))
        {
            BitmapSource bitmap = Imaging.CreateBitmapSourceFromHBitmap(
                img.GetHbitmap(),
                IntPtr.Zero,
                Int32Rect.Empty,
                BitmapSizeOptions.FromEmptyOptions());

            encoder.Frames.Add(BitmapFrame.Create(bitmap));
        }
    }

    static void SaveGif(GifBitmapEncoder encoder, string fileName)
    {
        using (FileStream stream = new FileStream(fileName, FileMode.Create))
        {
            encoder.Save(stream);
        }
    }
}

When I run it, memory usage starts at ~1MB, grows to ~800MB for the first 100 images; drops to ~400MB and grows to ~1.2GB for the second 100 images; drops to ~800MB and crashes with an out of memory error for the remaining 30 images.

I assume there is a memory leak somewhere, but I can't find it. GifBitmapEncoder, BitmapSource, and BitmapFrame are not IDisposable so I can't dispose any of those, and I don't see any other variables I use that could be. Maybe img.GetHbitmap() which returns a IntPtr?

Any thoughts?

I got the meat of my code from how to create an animated gif in .net.

Community
  • 1
  • 1
dx_over_dt
  • 13,240
  • 17
  • 54
  • 102
  • If you profile your application you can see exactly what is being allocated -- it will remove all doubt and saves us from pretending we're a compiler. – Jeroen Vannevel Mar 16 '16 at 20:14

2 Answers2

2

Try this:

        using (Bitmap img = (Bitmap)Bitmap.FromFile(fileName))
        {
            IntPtr hBitmap = img.GetHbitmap();

            BitmapSource bitmap = Imaging.CreateBitmapSourceFromHBitmap(
                hBitmap,
                IntPtr.Zero,
                Int32Rect.Empty,
                BitmapSizeOptions.FromEmptyOptions());

            encoder.Frames.Add(BitmapFrame.Create(bitmap));

            DeleteObject(hBitmap);
        }

The documentation: https://msdn.microsoft.com/en-us/library/1dz311e4(v=vs.110).aspx

You are responsible for calling the GDI DeleteObject method to free the memory used by the GDI bitmap object. For more information about GDI bitmaps, see Bitmaps in the Windows GDI documentation.

MusiGenesis
  • 74,184
  • 40
  • 190
  • 334
  • Oddly, this appears to be the solution looking at Task Manager, but I still hit the `OutOfMemory` exception in the same spot, despite only using 167MB of RAM. – dx_over_dt Mar 16 '16 at 20:25
  • I also noticed it only seems to do this after the last frame is added. And due to a bug in my code, I wasn't even saving the last gif at all, so it's throwing the error when trying to exit the program. – dx_over_dt Mar 16 '16 at 20:31
  • More accurately, it's failing when exiting the `foreach` loop on the file names. – dx_over_dt Mar 16 '16 at 20:37
  • And I just figured it out. There's a hidden file called thumbs.db that I wasn't filtering out. – dx_over_dt Mar 16 '16 at 20:39
0

Try with this :

encoder.Frames.Clear();
encoder = null;
GC.Collect();
//GifBitmapEncoder derives from DispatcherObject which has finalizer
GC.WaitForPendingFinalizers();
//then create a new instance
encoder = new GifBitmapEncoder();