0

Hey getting this error:

An exception of type 'System.OutOfMemoryException' occurred in System.Drawing.dll but was not handled in user code

Additional information: Out of memory.

It occurs in the below method on the DrawImage call

/// <summary>
        /// Resize the image to the specified width and height.
        /// </summary>
        /// <param name="image">The image to resize.</param>
        /// <returns>The resized image.</returns>
        public Bitmap ResizeImage(Image image, System.Drawing.Size newSize)
        {
            var destRect = new Rectangle(0, 0, newSize.Width, newSize.Height);
            var destImage = new Bitmap(newSize.Width, newSize.Height);

            destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);

            using (var graphics = Graphics.FromImage(destImage))
            {
                graphics.CompositingMode = CompositingMode.SourceCopy;
                graphics.CompositingQuality = CompositingQuality.HighQuality;
                graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                graphics.SmoothingMode = SmoothingMode.HighQuality;
                graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;

                using (var wrapMode = new ImageAttributes())
                {
                    wrapMode.SetWrapMode(WrapMode.TileFlipXY);
                    graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
                }
            }

            return destImage;
        }

I'm not sure why it occurs, I call this method multiple times, the result is converted into a Base64 String and stored within an ObservableCollection.

/// <summary>
        /// Convert Image and Resize
        /// </summary>
        /// <param name="loc"></param>
        /// <returns></returns>
        public async Task<string> GenerateThumbnailBinary(string loc)
        {
            return await Task<string>.Factory.StartNew(() =>
            {
                Image image = Image.FromFile(loc, true);

                // Figure out the ratio
                double ratioX = (double)Properties.Settings.Default.ThumbnailWidth.Width / (double)image.Width;
                double ratioY = (double)Properties.Settings.Default.ThumbnailWidth.Height / (double)image.Height;
                // use whichever multiplier is smaller
                double ratio = ratioX < ratioY ? ratioX : ratioY;

                System.Drawing.Size newSize = 
                new System.Drawing.Size(
                    (int)(image.Width * ratio), 
                    (int)(image.Height * ratio));

                Image resized = ResizeImage(image, newSize);

                return ImageToBase64(resized, ImageFormat.Jpeg);
            });
        }

I also display each of the strings back as an image by binding to the Collection and using a converter to convert the Base64 string back into a Bitmap, this is just for the UI to display what has been converted.

Where would my issue be starting? Could I be attempting to store too many images in memory when I display them on the UI and use the converter to convert the string to the image?

The high points in the image below obviously when it's running the method loop, but it still seems to stay higher than before the method is run at the end, do this help? enter image description here

Edit: This is the loop which starts the Tasks and runs the method.

// Generate List of images to upload
                var files = Directory.EnumerateFiles(sel.Name, "*.*", SearchOption.AllDirectories)
                        .Where(s => s.EndsWith(".jpeg") ||
                        s.EndsWith(".jpg") ||
                        s.EndsWith(".png") ||
                        s.EndsWith(".JPG"));
                int b = 1;

                if (files.Count() > 0)
                {
                    /// <summary>
                    /// Resize Images
                    /// </summary>
                    /// Set current Task first
                    UploadTask = Steps[0].Message;
                    try
                    {
                        foreach (string item in files)
                        {
                            // Generate new name
                            string oldname = Path.GetFileNameWithoutExtension(item);
                            string newName = Common.Security.KeyGenerator.GetUniqueKey(32);
                            string t = await GenerateThumbnailBinary(item);

                            ImageUploadObjects.Add(
                                new ImageUploadObject { OldName = oldname,
                                    NewName = newName,
                                    ByteImage = t });

                            UploadProgress = (int)Math.Round((double)(100 * b / files.Count()));
                            b++;
                        }
                        // Complete
                        Steps[0].Complete = true;
                    }
                    catch(Exception e)
                    {
                        Steps[0].Error = e.InnerException.ToString();
                    }


                    /// <summary>
                    /// Move full resoluation images
                    /// </summary>
                    /// Set current Task first
                    UploadTask = Steps[1].Message;
                    try
                    {
                        foreach (string item in files)
                        {

                        }
                        // Complete
                        Steps[1].Complete = true;
                    }
                    catch (Exception e)
                    {
                        Steps[1].Error = e.InnerException.ToString();
                    }
                }
            }

Edit:

How can I tell if the memory is still being used? I have added another image below, the first snapshot is before I execute the method, and the last one is when it finishes, 2 - 4 are whilst it's running enter image description here

Martyn Ball
  • 4,679
  • 8
  • 56
  • 126
  • 1
    You can get an OutOfMemoryException for a lot of reasons when dealing with bitmaps and drawing, for example http://stackoverflow.com/questions/6506089/system-drawing-out-of-memory-exception (I'm not suggesting this is a duplicate, just something to look at). – Eric J. Mar 14 '16 at 20:33
  • 2
    You need to dispose some of your variables (image and resized). By calling `.Dispose()` or by using `using` keyword. Be carefull to not call this method too many time at the same time. – Kalten Mar 14 '16 at 20:33
  • Are the image sizes within reason? – BitTickler Mar 14 '16 at 20:34
  • I will make sure I Dispose of the variables, and yes the sizes are all the same – Martyn Ball Mar 14 '16 at 20:35
  • I'm no expert on how C# handles async, but I have a hunch that since you are asynchronously dealing with the drawing, it is very possible to have a lot of memory held while the thread holding it is waiting, especially if you call GenerateThumbnailBinary many times. Would it be possible to pass in all of the string locations at once and do the work on all of the images in one async thread rather than making multiple threads? – Gordon Allocman Mar 14 '16 at 20:41
  • Updated post with Memory Usage, that's with disposing of `image` – Martyn Ball Mar 14 '16 at 20:41
  • @GordonAllocman I have been told that Tasks is the new and better way to do things like this, as I want UI Updates, if I passed them all I wouldn't get UI Updates or I would have to update the UI by using Invoke which I have been told is the old way of doing things. – Martyn Ball Mar 14 '16 at 20:43
  • 2
    [Don't use `Task.Factory.StartNew` without specifing a task scheduller](http://blog.stephencleary.com/2013/08/startnew-is-dangerous.html) use `Task.Run(` instead. – Scott Chamberlain Mar 14 '16 at 20:59
  • @MartynBall Can I see the code which calls this method? While it is true Task is the new way but this can cause issues too. Let me see your calling code please and I may be able to tell you why. – CodingYoshi Mar 14 '16 at 21:00
  • @CodingYoshi Add to post – Martyn Ball Mar 14 '16 at 21:02
  • @MartynBall What diagnostic tool is that above where the memory and GC is displayed? I'm using VS2013 Community is that available in a different edition? – khargoosh Mar 14 '16 at 21:35
  • @khargoosh It's the one built into VS2015 – Martyn Ball Mar 14 '16 at 21:43
  • @ScottChamberlain I have also changed it to `Task.Run` – Martyn Ball Mar 14 '16 at 22:22

2 Answers2

1

I think the easiest way to solve your issue is to do it in chunks/batches. For example, if you have 100 files, you are creating 100 tasks, which load the file content into images into memory. Perhaps do 10 (or some other number) and once that has been completely done, do the next 10 (or some other number). I am sure this will fix your issue.

Also make sure to call Dispose on any class which implements Disposable, i.e., Image and Bitmap etc.

In addition to the above, here is summarily what you are trying to do: 1. Read a directory and take all the files. 2. Create thumbnail images for each file. 3. Add thumbnail to a collection in memory. 4. Transfer the images to another location.

For item 2 above, I would not keep all the thumbnails in memory. Even if I need to show this in UI, I will incorporate paging and pull them as needed.

CodingYoshi
  • 25,467
  • 4
  • 62
  • 64
  • It only happens sometimes, what is the best way to test this for slower machines? I have 32GB's 2133MHz in my system so it's hard to tell if a cheaper system will not handle it so well. – Martyn Ball Mar 14 '16 at 21:50
  • See the edited answer. I think you can improve your design as suggested before worrying to test it on a slower machine. – CodingYoshi Mar 14 '16 at 22:10
  • I have made them MUCH smaller (about 200 pixels wide) and they are stored as a string so didn't think displaying them would be much of an issue, there is never more than 100 really to be honest, should always be less than that. – Martyn Ball Mar 14 '16 at 22:12
  • 1
    OK. Well the next thing I would do is test my code with just 1 file. [Here](https://msdn.microsoft.com/en-us/library/dn342825.aspx) you can read more about which objects are rooted and why (read the section Paths to root). Rooted objects will not be collected until they are no longer rooted. Take a snapshot before you create thumbnail, and see what is rooted. Then take snapshot after you create thumbnail. Compare the 2 (manually) to see what is still rooted. If you have objects which are not being collected because they are rooted for one file, the same will be true for many. Try that. – CodingYoshi Mar 14 '16 at 22:29
  • Thanks will try this tomorrow, must get some sleep as have work! – Martyn Ball Mar 14 '16 at 22:33
0

The Image resized = ResizeImage(image, newSize) is not being disposed of. Because the memory allocated will not be released until the finalizer thread is run, you could be leaking memory all over the place.

Image image = Image.FromFile(loc, true);
...

Image resized = ResizeImage(image, newSize);
image.Dispose();
string base64Image = ImageToBase64(resized, ImageFormat.Jpeg);
resized.Dispose();
return base64Image;
PhillipH
  • 6,182
  • 1
  • 15
  • 25
  • How can I dispose of that as I must return it? – Martyn Ball Mar 14 '16 at 21:18
  • You arent returning it - you are passing it to ResizeImage function. Its the return value of ResizeFunction you are returning. Image resized = ResizeImage(image, newSize); image.Dispose(); return resized; – PhillipH Mar 14 '16 at 21:21
  • So, if I instead did: `Image resized = ResizeImage(image, newSize); image.Dispose(); string k = ImageToBase64(resized, ImageFormat.Jpeg); resized.Dispose();return k;` would that be better? As I Dispose of the Image and return from the Task with the string. – Martyn Ball Mar 14 '16 at 21:41
  • How can I tell if the memory is still being used? I have added another image to my post, the first snapshot is before I execute the method, and the last one is when it finishes, 2 - 4 are whilst it's running – Martyn Ball Mar 14 '16 at 22:10
  • I've added a code snippet that disposes of the unmanaged resources in my answer. Are you still getting OutOfMemoryException when you implement this change ? – PhillipH Mar 15 '16 at 10:50