Search code examples
c#.netmemory-leaksphotos

Is there an efficient way of handling high-res photos in C#?


I've made a program that analyzes the first pixel of an image and then notes the values of it in a List, this is for checking if the image is black&white or in color. Now, does anyone know an efficient way of reading high-res images? Right now I'm using Bitmaps but they are highly inefficient. The images are around 18 megapixels each and I want to analyze around 400 photos. Code below:

Bitmap b;

foreach (FileInfo f in files) {
  // HUGE MEMORY LEAK
  System.GC.Collect();
  System.GC.WaitForPendingFinalizers();

  b = (Bitmap)Bitmap.FromFile(f.FullName);

  // reading pixel (0,0) from bitmap

When I run my program it says:

"An unhandled exception of type 'System.OutOfMemoryException' occurred in System.Drawing.dll
Additional information: There is no available memory."

I've tried with System.GC.Collect() to clean up, as you can see, but the exception doesn't go away. If I analyse a folder that contains only a few photos, the program runs fine and gladly does it's job.


Solution

  • Using the first pixel of an image to check if it is colour or not is the wrong way to do this.

    If you have an image with a black background (pixel value 0,0,0 in RGB), how do you know the image is black and white, and not colour with a black background?

    Placing the bitmap in a Using is correct, as it will dispose properly.

    The following will do the trick.

    class Program
    {
        static void Main(string[] args) {
            List<String> ImageExtensions = new List<string> { ".JPG", ".JPE", ".BMP", ".GIF", ".PNG" };
    
            String rootDir = "C:\\Images";
            foreach (String fileName in Directory.EnumerateFiles(rootDir)) {
                if (ImageExtensions.Contains(Path.GetExtension(fileName).ToUpper())) {
                    try {
                        //Image.FromFile will work just as well here.
                        using (Image i = Bitmap.FromFile(fileName)) {
                            if (i.PixelFormat == PixelFormat.Format16bppGrayScale) {
                                //Grey scale...
                            } else if (i.PixelFormat == PixelFormat.Format1bppIndexed) {
                                //1bit colour (possibly b/w, but could be other indexed colours)
                            }
                        }
                    } catch (Exception e) {
                        Console.WriteLine("Error - " + e.Message);
                    }
                }
            }
        }
    }
    

    The reference for PixelFormat is found here - https://msdn.microsoft.com/en-us/library/system.drawing.imaging.pixelformat%28v=vs.110%29.aspx

    Objects in C# are limited to 2Gb, so I doubt that an individual image is causing the problem. I also would suggest that you should NEVER manually call the GC to solve a memory leak (though this is not technically a leak, just heavy memory usage).

    Using statements are perfect for ensuring that an object is marked for disposal, and the GC is very good at cleaning up.

    We perform intensive image processing in our software, and have never had issues with memory using the approach I have shown.

    While simply reading the header to find image data is a perfectly correct solution, it does mean a lot of extra work to decode different file types, which is not necessary unless you are working with vast amounts of images in very small memory (although if that is your aim, straight C is a better way to do it rather than C#. Horses for courses and all that jazz!)

    EDIT - I just ran this on a directory containing over 5000 high-res TIFFs with no memory issues. The slowest part of the process was the console output!