locked
OutOfMemoryException when using Graphics.FromImage RRS feed

  • Question

  • I have an application used to modify an image and add text on it, and it support undo operation.
    But, when I clone the original image (to support the undo operations), it throw OutOfMemoryException when using Graphics.FromImage on the original image.
    Here is a very simplified program with the bug :
     
    using System;  
    using System.Drawing;  
    using System.Windows.Forms;  
    using System.IO;  
     
    namespace TestImage  
    {  
        /// <summary> 
        /// Description résumée de Form1.  
        /// </summary> 
        public class Form1 : System.Windows.Forms.Form  
        {  
            private System.Windows.Forms.Button btnLoad;  
            private System.Windows.Forms.OpenFileDialog openFileDialog1;  
            /// <summary> 
            /// Variable nécessaire au concepteur.  
            /// </summary> 
            private System.ComponentModel.Container components = null;  
     
            public Form1()  
            {  
                //  
                // Requis pour la prise en charge du Concepteur Windows Forms  
                //  
                InitializeComponent();  
     
                //  
                // TODO : ajoutez le code du constructeur après l'appel à InitializeComponent  
                //  
            }  
     
            /// <summary> 
            /// Nettoyage des ressources utilisées.  
            /// </summary> 
            protected override void Dispose( bool disposing )  
            {  
                if( disposing )  
                {  
                    if (components != null)   
                    {  
                        components.Dispose();  
                    }  
                }  
                base.Dispose( disposing );  
            }  
     
            #region Code généré par le Concepteur Windows Form  
            /// <summary> 
            /// Méthode requise pour la prise en charge du concepteur - ne modifiez pas  
            /// le contenu de cette méthode avec l'éditeur de code.  
            /// </summary> 
            private void InitializeComponent()  
            {  
                this.btnLoad = new System.Windows.Forms.Button();  
                this.openFileDialog1 = new System.Windows.Forms.OpenFileDialog();  
                this.SuspendLayout();  
                //   
                // btnLoad  
                //   
                this.btnLoad.Anchor = System.Windows.Forms.AnchorStyles.None;  
                this.btnLoad.Location = new System.Drawing.Point(72, 128);  
                this.btnLoad.Name = "btnLoad";  
                this.btnLoad.Size = new System.Drawing.Size(152, 23);  
                this.btnLoad.TabIndex = 0;  
                this.btnLoad.Text = "Load And Clone Image";  
                this.btnLoad.Click += new System.EventHandler(this.btnLoad_Click);  
                //   
                // Form1  
                //   
                this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);  
                this.ClientSize = new System.Drawing.Size(292, 273);  
                this.Controls.Add(this.btnLoad);  
                this.Name = "Form1";  
                this.ResumeLayout(false);  
     
            }  
            #endregion  
     
            /// <summary> 
            /// Point d'entrée principal de l'application.  
            /// </summary> 
            [STAThread]  
            static void Main()   
            {  
                Application.Run(new Form1());  
            }  
     
            private void btnLoad_Click(object sender, System.EventArgs e)  
            {  
                openFileDialog1.ShowDialog(this);  
                String strFileName = openFileDialog1.FileName;  
                if (strFileName.Length > 0)  
                    CloneAndGetGraphicsFromOriginalImage(strFileName);  
            }  
     
            private void CloneAndGetGraphicsFromOriginalImage(String strFileName)  
            {  
                FileStream stream = File.Open(strFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);  
                Image OriginalImage = Image.FromStream(stream);  
                stream.Close();  
     
                //If I remove this line, it will work fine (but in the real application, I need it because I want to permit undo operation)  
                Image imgClone = (Image)(OriginalImage.Clone()); // With this line, the next line will throw OutOfMemoryException  
     
                Graphics g = Graphics.FromImage(OriginalImage); // <-- throw OutOfMemoryException when OriginalImage has been cloned !  
                  
     
     
                g.DrawString("test", DefaultFont, new SolidBrush(Color.Black), 0, 0);  
                this.BackgroundImage = OriginalImage;  
            }  
        }  
    }  
     
    Monday, November 10, 2008 12:33 AM

Answers

  •  I think this is because you are closing the image file too soon.  Thy this instead...
            private void CloneAndGetGraphicsFromOriginalImage(String strFileName)  
            {  
                using (FileStream stream = File.Open(strFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))  
                {  
                    Image OriginalImage = Image.FromStream(stream);  
                    Image imgClone = (Image)(OriginalImage.Clone()); // With this line, the next line will throw OutOfMemoryException     
                    Graphics g = Graphics.FromImage(OriginalImage); // <-- throw OutOfMemoryException when OriginalImage has been cloned !     
                    g.DrawString("test", DefaultFont, new SolidBrush(Color.Black), 0, 0);  
                    this.BackgroundImage = OriginalImage;  
                }  
            }  
     

    Les Potter, Xalnix Corporation, Yet Another C# Blog
    Monday, November 10, 2008 1:02 AM
  • That's strange because your code example still throws an out-of-memory exception on my system.  But another way of getting around the problem is to use Image.FromFile(strFileName) rather than the .FromStream(...).
    Les Potter, Xalnix Corporation, Yet Another C# Blog
    Tuesday, November 11, 2008 3:09 AM
  • According to the documentation for FromStream(), you must keep the stream open for the life of the Image (until you Dispose() the Image).  This is according to .NET Framework 3.5 docs.  If it works for you, it may be only by chance and come back and haunt you later.  I think standard or best practice is to create a temporary working file of anything you wish to "edit" and then apply saves to the original file as the user directs.
    Les Potter, Xalnix Corporation, Yet Another C# Blog
    Tuesday, November 11, 2008 2:40 PM

All replies

  •  I think this is because you are closing the image file too soon.  Thy this instead...
            private void CloneAndGetGraphicsFromOriginalImage(String strFileName)  
            {  
                using (FileStream stream = File.Open(strFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))  
                {  
                    Image OriginalImage = Image.FromStream(stream);  
                    Image imgClone = (Image)(OriginalImage.Clone()); // With this line, the next line will throw OutOfMemoryException     
                    Graphics g = Graphics.FromImage(OriginalImage); // <-- throw OutOfMemoryException when OriginalImage has been cloned !     
                    g.DrawString("test", DefaultFont, new SolidBrush(Color.Black), 0, 0);  
                    this.BackgroundImage = OriginalImage;  
                }  
            }  
     

    Les Potter, Xalnix Corporation, Yet Another C# Blog
    Monday, November 10, 2008 1:02 AM
  • In the real application, I clone the image each time user modify it (to permet undo operation). And I must close the stream because when the user will save the result I may save over the original image.

    But, I found the problem !
    I have to call the function like this :

    Image OriginalImage = Image.FromStream(stream, true);
    The last parameter (true) seem to be very important when we close the stream.

    private void CloneAndGetGraphicsFromOriginalImage(String strFileName)
    {
       FileStream stream = File.Open(strFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
       Image OriginalImage = Image.FromStream(stream,
    true); // Very Important to set true if we close the stream after
       stream.Close();

       Image imgClone = (Image)(OriginalImage.Clone());
       Graphics g = Graphics.FromImage(OriginalImage);

       g.DrawString("test", DefaultFont,
    new SolidBrush(Color.Black), 0, 0);
       this.BackgroundImage = OriginalImage;
    }

    Thank You.

    Tuesday, November 11, 2008 12:43 AM
  • That's strange because your code example still throws an out-of-memory exception on my system.  But another way of getting around the problem is to use Image.FromFile(strFileName) rather than the .FromStream(...).
    Les Potter, Xalnix Corporation, Yet Another C# Blog
    Tuesday, November 11, 2008 3:09 AM
  •  Image.FromFile(strFileName) is the first thing I tried, but the problem is the image file still "opened" by the application and then, when user want to save the new image over the original image on disk, we got "file in use" error.
    Tuesday, November 11, 2008 4:30 AM
  • According to the documentation for FromStream(), you must keep the stream open for the life of the Image (until you Dispose() the Image).  This is according to .NET Framework 3.5 docs.  If it works for you, it may be only by chance and come back and haunt you later.  I think standard or best practice is to create a temporary working file of anything you wish to "edit" and then apply saves to the original file as the user directs.
    Les Potter, Xalnix Corporation, Yet Another C# Blog
    Tuesday, November 11, 2008 2:40 PM
  • This is an old thread, but I have the real solution...

    When you create an image using a MemoryStream -> BinaryWriter -> Image.FromStream, you need to leave the MemoryStream and BinaryWriter open or you will get this OutOfMemory exception when you try to get the Graphics object - Even if you Clone() the image, and then try to get the Graphics object from the clone.  I've tested out various scenarios and the BEST SOLUTION is this...

    Use this alternative Clone method instead of the built in one...

    Image CloneImage(Image img)
    {
        Bitmap img2 = new Bitmap(img.Width, img.Height);
        using (Graphics g = Graphics.FromImage(img2);
            g.DrawImageUnscaled(img, 0, 0);
        return img2;
    }

    Because the "clone" is created completely seperate from the MemoryStream and BinaryWriter, there is no dependence on them being open and you can freely write to the new image (and close the original stream and writer).  Hope this helps others - quite a pain with the bad error info (not really a memory issue).

    - Tom

    • Proposed as answer by tcksoft Friday, December 16, 2011 7:34 AM
    Friday, December 16, 2011 7:33 AM
  • There is a minor typo in @tcksoft code [a ';' instead of a ')' in the using statement] but it works like a dream. I was having the same issue with an image downloaded from Blob storage on Azure with Webclient.OpenRead- A subseqent Graphics.FromImage worked with PNGs but threw the out of memory exception on JPGs. Cloning it first with this solves the issue. Thx.
    Friday, June 29, 2012 9:23 AM
  • Oh, wow, thank you tcksoft!  I've been wrestling with this misleading Out of Memory exception all day.

    However, I suggest that "clone" isn't the right term (if that Image was a Metafile, your new Bitmap isn't a proper clone)... rather, it's a measurement equivalent.  And I don't really need to draw the image in there... that drawing effort is a waste... all I need it for is to do Graphics.FromImage.

    So, instead I have decided to follow PrintDocument's lead:  provide a CreateMeasurementGraphics() function that can be called on any Image... not just Images that are still "live" and can be drawn upon.

            /// <summary>
            /// Use this to get a Graphics' device context for an Image that may not be "live".
            /// You can't draw on the Image with this Graphics, but you can use it to get a device context (GetHdc).
            /// In contrast, you can only use Graphics.FromImage if the Stream the Image was created from still lives;
            /// otherwise you will get "Out of Memory" exceptions.
            /// </summary>
            /// <param name="image">The Image from which to get the Measurements.</param>
            public static Graphics CreateMeasurementGraphics(Image image)
            {
                Bitmap bm = new Bitmap(image.Width, image.Height, image.PixelFormat); // standalone image of same measurements as image
                return Graphics.FromImage(bm);
            }


    • Edited by TCC Developer Saturday, August 18, 2012 5:45 AM clarifying
    Saturday, August 18, 2012 5:24 AM
  • Actually, after coding some with that, I think its better to avoid any other code even touching the semi-managed GetHdc() IntPtr.  I've switched to using this that keeps that cruftiness hidden away.

            public static Metafile NewMetafile(Stream stream, int width, int height, PixelFormat pix)
            {
                using (Bitmap bm = new Bitmap(width, height, pix))
                using (Graphics g = Graphics.FromImage(bm))
                {
                    IntPtr hdc = g.GetHdc(); // gets released by g.Dispose() called by using
                    return new Metafile(stream, hdc, EmfType.EmfPlusOnly);
                }
            }
    


    Sunday, August 19, 2012 2:18 PM
  • Hi Guys,
      In Graphics, "out of memory" Exception mostly occurred when the image.propertyitems throws some exception. At that, time we need to store the image in stream and again get the image from the stream.
    Please use the below codes for resolve the exception.
    //Graphics not support the Indexed image
    private string[] m_indexedpixel_notsupport = { "Format1bppIndexed", "Format4bppIndexed", "Format8bppIndexed"};
    if (indexed == -1 && image.PropertyIdList.Length == 0)
          {
              MemoryStream stream = new MemoryStream();
              // Save image to stream.
              image.Save(stream, ImageFormat.Bmp);
              //The input image having error so we need to resave it
              Image temp_img = Image.FromStream(stream);
              //Resaved image assign to input image
              image = temp_img;
          }
          if (indexed == -1  )
          {
              //That image handled by graphics
              Graphics gr = Graphics.FromImage(image);
              GraphicsUnit unit = GraphicsUnit.Pixel;
              RectangleF rect = image.GetBounds(ref unit);
              gr.DrawImage(image, rect);
              gr.Dispose();
            }

    Thanks ,

    Rajesh A.

    Syncfusion


    Friday, November 9, 2012 5:11 AM