3

Hi I am trying to upload some images async, I get Out of Memory Exception, I am disposing by using statement however I get following stack trace

[OutOfMemoryException: Out of memory.]
   System.Drawing.Graphics.CheckErrorStatus(Int32 status) +1146420
   System.Drawing.Graphics.DrawImage(Image image, Rectangle destRect, Int32 srcX, Int32 srcY, Int32 srcWidth, Int32 srcHeight, GraphicsUnit srcUnit, ImageAttributes imageAttrs, DrawImageAbort callback, IntPtr callbackData) +256
   System.Drawing.Graphics.DrawImage(Image image, Rectangle destRect, Int32 srcX, Int32 srcY, Int32 srcWidth, Int32 srcHeight, GraphicsUnit srcUnit, ImageAttributes imageAttr) +48

Here is resizer, where I get exception:

public Bitmap ResizeImage(Image image, int width, int height)
        {
          var newWidth = (int)(imageWidth * ratio) < 210 ? 210 : (int)(imageWidth * ratio);
          var newHeight = (int)(imageHeight * ratio) < 210 ? 210 : (int)(imageHeight * ratio);
            //Image resize logic

            var destRect = new Rectangle(0, 0, newWidth, newHeight);
            var destImage = new Bitmap(newWidth, newHeight);
            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);
                    /*Here I get error*/graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel,
                        wrapMode);
                }
            }

            return destImage;
        }

Here which uploads image:

private async Task<short> UploadImage(string title, HttpPostedFileBase file, short dimensionWidth,
            short dimensionHeight)
        {
            var blockBlob = CloudBlobContainer.GetBlockBlobReference(title);
            var jpgInfo = ImageCodecInfo.GetImageEncoders().First(codecInfo => codecInfo.MimeType == "image/jpeg");
            using (var image = Image.FromStream(file.InputStream, true, true))
            {
                using (var stream = new MemoryStream())
                using (var encParams = new EncoderParameters(1))
                {
                    encParams.Param[0] = new EncoderParameter(Encoder.Quality, 60L);
                    if (image.Width > dimensionWidth && image.Height > dimensionHeight)
                        using (Bitmap bitMapImage = ResizeImage(image, dimensionWidth, dimensionHeight))
                        {
                            bitMapImage.Save(stream, jpgInfo, encParams);
                        }
                    else
                    {
                        image.Save(stream, jpgInfo, encParams);
                    }
                    stream.Position = 0;
                    await blockBlob.UploadFromStreamAsync(stream);
                    blockBlob.Properties.CacheControl = "public, max-age=864000";
                    blockBlob.SetProperties();
                }
            }
            return (short)EnumData.EOpStatus.Success;
        }

Here is main function:

        public async Task<string> UploadImages(string title, IEnumerable<HttpPostedFileBase> files, short fileCount)
        {
            var fileIndex = 0;
            var imageCsv = String.Empty;
            var uploadTask = new Task<short>[fileCount * 2];
            foreach (var file in files)
            {
                var fullTitle = title + "-" + Convert.ToString(fileIndex) + Path.GetExtension(file.FileName);
                uploadTask[fileIndex] = UploadImage(fullTitle, file, 1440, 900);
                uploadTask[fileIndex + 1] = UploadImage("thumb-" + fullTitle, file, 280, 280);
                imageCsv += String.IsNullOrEmpty(imageCsv) ? fullTitle : "," + fullTitle;
                /*await Task.WhenAll(uploadTask[fileIndex], uploadTask[fileIndex + 1]);*///Works fine in this case 

                fileIndex += 2;
            }
            await Task.WhenAll(uploadTask);
            return imageCsv;
        }

So after some uploads I get an error

Performance Monitor seems normal through out application use, I think its normal enter image description here

Sandip Bantawa
  • 2,822
  • 4
  • 31
  • 47
  • Have you tried running Visual Studio's Memory Analysis tools? –  Nov 23 '15 at 07:07
  • 3
    That might not be an actual OOM error. GDI+ likes returning such an error when you pass it invalid parameters. – CodesInChaos Nov 23 '15 at 07:20
  • @Micky nope trying with performance monitor now though – Sandip Bantawa Nov 23 '15 at 07:40
  • That reminds me - there is a limit to GDI objects irrespective to the amount of free RAM you have. http://stackoverflow.com/questions/9723470/whats-the-upper-limit-on-gdi-objects-for-one-process-in-windows-7. Though you seem to be doing a pretty good job managing them –  Nov 23 '15 at 07:57
  • @Micky increasing to 1024 didnt work :-( – Sandip Bantawa Nov 23 '15 at 08:29
  • GDI+ will also return such an error if you fail to release GDI+ resources. Use Visual Studio's profiler to see how many Image object were allocated, how many orphans etc. VS 2015's profiler even allows you to take and compare memory snapshots and see how many new object were created, deleted, etc – Panagiotis Kanavos Nov 23 '15 at 11:19
  • @PanagiotisKanavos can you see edits? – Sandip Bantawa Nov 24 '15 at 16:11
  • 2
    When working with GDI (which Bitmap is just a wrapper for) `OutOfMemoryException` would be better named `OutOfHandlesException` or `OutOfUnManagedMemoryException` , it has often has nothing to do with # bytes allocated by the CLR. It is all unmanaged memory and GDI handles. However, looking at your code I don't see any obvious mistakes that would cause a memory or handle leak. Are you working with very large images? – Scott Chamberlain Nov 24 '15 at 16:15
  • @ScottChamberlain single image is 8-10 MB, however it uploads 700 KB i.e. compressed before it hit outofmemory around 3-4 image of that size – Sandip Bantawa Nov 24 '15 at 16:23
  • Failing after 700k sounds like a slow handle leak somewhere (or I means the 700,001th file is giant and is causing it to use up all the unmanaged memory, but that is unlikely). – Scott Chamberlain Nov 24 '15 at 16:24
  • @ScottChamberlain Its 700KB of image on azure blobs but that might be irrelevant??, 3-4 image is processed before outofmemory of image with size of 8-10 MB/image – Sandip Bantawa Nov 24 '15 at 16:26
  • GDI is also infamous for throwing an OutOfMemoryException when it doesn't like something about the image file, like the pixel format, or if the file is corrupted. – Bradley Uffner Nov 24 '15 at 17:08
  • @BradleyUffner pixel format is uniform and image is not corrupted :-( – Sandip Bantawa Nov 24 '15 at 17:13
  • How is the destination rect calculated? If your destination rect is 0 width or 0 height, or your source dimensions yields a rect of 0 width/height then you will get an OutOfMemoryException. Basically GDI throws that exception any time you try to draw to a space smaller than a pixel. If your exception was thrown when you initialized your source bitmap, I would put money on the source file having dimensions too big to generate the GDI bitmap object. Upwards of 10,000 pixels per side is enough to get you there. – Berin Loritsch Dec 21 '15 at 16:16
  • @BerinLoritsch check my edits, if the image smaller than we have defaults there. – Sandip Bantawa Dec 21 '15 at 16:38
  • If it's not every file causing you a problem, then I highly recommend using a logger like Log4Net and logging everything you can about each image you are processing. It might be something about that source file, or it may be a cumulative effect problem. If you know what file the issue is happening with, the next time make it the only file in the upload. If it's still a problem, then there is likely something about that file. Otherwise, you might have to start the painful process of trying to find the handle leaks. – Berin Loritsch Dec 21 '15 at 16:43
  • @BerinLoritsch I do use log4net but I do see that problem in my test code, its not about file, I have tried different combination of same sizes and its same – Sandip Bantawa Dec 21 '15 at 16:49
  • If you try to paint outside the rectangle, outofmemory is thrown also – Leonardo Dec 21 '15 at 17:42
  • @Leonardo image is scaled to fit the rectangle as per https://msdn.microsoft.com/en-us/library/ms142045(v=vs.110).aspx – Sandip Bantawa Dec 21 '15 at 18:02

2 Answers2

0

I believe you are indeed trying to paint outside the destination rectangle... if you notice your code you are in fact (in the line marked) painting image.Width and image.Height (original dimensions), and not the ratio-applied dimensions...

Leonardo
  • 10,737
  • 10
  • 62
  • 155
0

Dispose sourceImage and destImage after job is done.

Ali Dehqan
  • 461
  • 1
  • 8
  • 18
  • Your answer looks Ok to me, but could you explain a bit more why that works and maybe provide a code snippet how to do this? That would make it much easier for future visitors of this post to get to their solution. Thanks. – Filnor Jun 06 '18 at 06:49