Martyn Ball
Martyn Ball

Reputation: 4895

Think I have a memory leak

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

Upvotes: 0

Views: 225

Answers (2)

PhillipH
PhillipH

Reputation: 6222

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;

Upvotes: 0

CodingYoshi
CodingYoshi

Reputation: 27039

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.

Upvotes: 1

Related Questions