none
Will loading a JPG into a Metafile consume less memory than loading it into a Bitmap? RRS feed

  • Question

  • I changed the title and this opening to reflect where this thread ended up going.  Interesting discussion, but never got to the question I really wanted to ask.  I'll create a new thread for that original question.

    The discussion in this thread is on whether there is a memory savings (at the cost of some drawing time) in loading a JPG into a Metafile (which holds it just as a single drawing command with the compressed JPG) vs loading that same JPG into a Bitmap (which holds it as an uncompressed pixel array).

    The Original Question (which is now asked more concisely in a separate thread: http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/9858edc9-98f5-46d9-8960-9c076dabb124 ):

    I know I can load a JPG file into a Bitmap, and then I can use Graphics.DrawImage() to draw that Bitmap into a Metafile, and that the Metafile will just have the compressed JPG in it... and then I can discard the Bitmap, freeing up the memory consumed by the uncompressed bitmap.  Great.

    But I'd rather not ever generate that uncompressed Bitmap as it seems it will result in fragmenting of the unmanaged memory where that was temporarily stored... plus sometimes they are HUGE enough to run out of memory, even though the JPG is not all that big.

    Sooo, is there a way to Draw a JPG into a Metafile without first uncompressing it into a Bitmap??




    Thursday, March 21, 2013 10:44 PM

Answers

  • The OP has changed this thread's subject so I will answer the current subject:  "Will loading a JPG into a Metafile consume less memory than loading it into a Bitmap"

    The answer is yes.

    I constructed a 10240 x 7680 image and saved it to a JPEG file.  I also constructed a metafile containing the JPEG and commands to draw the image.  I made two projects, one loading the JPEG into a PictureBox and the other loading the metafile into a PictureBox.  The image below shows the result:

    The program using the JPEG uses about 256 MB while the program using the metafile uses about 32 MB.

    A solution containing the projects with the files are on my SkyDrive in the "CompareJpgAndEmf.zip" file.

    • Marked as answer by TCC Developer Friday, March 29, 2013 4:07 AM
    Monday, March 25, 2013 3:45 PM

All replies

  • The metafile records an instruction to draw the bitmap.  It stores an uncompressed image of the original file.  The best you can do is decode and render the JPEG as required.  A metafile, which is an ancient container, won't help.

    Thursday, March 21, 2013 10:57 PM
  • The metafile records an instruction to draw the bitmap.  It stores an uncompressed image of the original file.  The best you can do is decode and render the JPEG as required.  A metafile, which is an ancient container, won't help.

    Actually, the instruction in the Metafile to draw the Bitmap holds the COMPRESSED image, surprisingly enough.  I have tested this numerous ways... examined memory profilers... written things out... serialized things out... and they all point to the same conclusion:  the Metafile's recorded command just has the compressed JPG in it... not the huge uncompressed Bitmap.

    I just don't know how to do that without creating the uncompressed Bitmap temporarily.

    Thursday, March 21, 2013 11:05 PM
  • "Actually, the instruction in the Metafile to draw the Bitmap holds the COMPRESSED image, surprisingly enough."

    Do you have code that demonstrates this?  My metafiles with bitmaps store uncompressed images or at least the size of the metafile is large enough to hold an uncompressed image. 


    • Edited by JohnWein Thursday, March 21, 2013 11:12 PM
    Thursday, March 21, 2013 11:08 PM
  • "Actually, the instruction in the Metafile to draw the Bitmap holds the COMPRESSED image, surprisingly enough."

    Do you have code that demonstrates this?  My metafiles with bitmaps store uncompressed images or at least the size of the metafile is large enough to hold an uncompressed image. 

    If you just build an uncompressed Bitmap from drawing commands or such, then yes it will be uncompressed when drawn to the Metafile.  But if you load the Bitmap from a compressed file using Image.FromFile(), then the Bitmap hangs onto the compressed file in addition to its uncompressed pixel array.  And then if you draw that Bitmap into a Metafile, it only puts the compressed data into the Metafile.

    If you run the code below in a Memory Profiler, you'll see that the Image.FromFile(3.jpg) adds two large objects into the unmanaged memory:  the 42.5MB <Unidentified> which is very close in size to 3*NumberPixels...  hence it is the uncompressed pixel array; and the 10MB 3.jpg which is 20 bytes more than the JPG on disk...  hence it is the compressed JPG data.  If you then draw that Bitmap into a Metafile and Dispose of the Bitmap, you'll see the <Unidentified> pixel array vanish, but the 10MB block gets moved from Other Data into Unidentified Unmanaged Heap.  And nothing else large shows up... and since the Metafile put into a PictureBox shows the JPG image, it must be the case that the Metafile is using that compressed JPG data.

    Am I missing something?

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.IO;
    using System.Text;
    using System.Windows.Forms;
     
    namespace TestEMF
    {
        public partial class Form1 : Form
        {
            private Metafile TestEmf;
            private Image testImg;
            private Bitmap testBitmap;
            private byte[] testByteArray;
     
            // 10,016 kb reported on disk
            // 3240 x 4320
            // Expecting 41990400 bytes for unexpanded raw memory?
            private static String test3Path = @"c:\testdata2\3.jpg"; 
           
            public Form1()
            {
                InitializeComponent();
     
            }
     
     
            #region Misc Helpers
     
            /// <summary>
            /// Returns a new empty EMF+ Metafile recording to an inaccessible memory position.
            /// You will only be able to get the Graphics for this Metafile once to draw on it.
            /// And you will only be able to use the resulting Metafile to draw into other things.
            /// If you want to be able to store the Metafile or write it out, pass in a 'stream' argument.
            /// Note that this Metafile should not be used until first you get a Graphics and close it;
            /// until then, you will get "Parameter not valid." Exceptions if you try to use it.
            /// </summary>
            public static Metafile NewMetafile()
            {
                using (Graphics g = Graphics.FromHwnd(IntPtr.Zero))  // offscreen device context
                {
                    IntPtr hdc = g.GetHdc(); // gets released by g.Dispose() called by using g
                    return new Metafile(hdc, EmfType.EmfPlusOnly);
                }
            }
     
     
            /// <summary>
            /// Returns a new empty EMF+ Metafile recording to 'stream' (typically a FileStream or MemoryStream).
            /// </summary>
            /// <param name="stream">The stream to record the drawing to.</param>
            public static Metafile NewMetafile(Stream stream)
            {
                using (Graphics g = Graphics.FromHwnd(IntPtr.Zero))  // offscreen device context
                {
                    IntPtr hdc = g.GetHdc(); // gets released by g.Dispose() called by using g
                    return new Metafile(stream, hdc, EmfType.EmfPlusOnly);
                }
            }
     
     
            /// <summary>
            /// Returns a new empty EMF+ Metafile recording to 'stream' (typically a FileStream or MemoryStream).
            /// It is scaled to the specified width and height.
            /// </summary>
            /// <param name="stream">The stream to record the drawing to.</param>
            /// <param name="width">The width to use for scaling.</param>
            /// <param name="height">The height to use for scaling.</param>
            /// <param name="pix">The PixelFormat to use for scaling.</param>
            public static Metafile NewMetafile(Stream stream, int width, int height)
            {
                using (Bitmap bm = new Bitmap(width, height))
                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);
                }
            }
     
     
            /// <summary>
            /// Returns a new empty EMF+ Metafile recording to 'stream' (typically a FileStream or MemoryStream).
            /// It is scaled to the specified width, height, and PixelFormat.
            /// </summary>
            /// <param name="stream">The stream to record the drawing to.</param>
            /// <param name="width">The width to use for scaling.</param>
            /// <param name="height">The height to use for scaling.</param>
            /// <param name="pix">The PixelFormat to use for scaling.</param>
            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);
                }
            }
     
     
            /// <summary>
            /// Returns a new EMF+ Metafile that contains just one drawing command:  a copy of the argument Image.
            /// </summary>
            /// <param name="image">The Image to be the sole object drawn in the Metafile.</param>
            public static Metafile NewMetafileContaining(Image image)
            {
                return NewMetafileContaining(image, image.Width, image.Height);
            }
     
     
            /// <summary>
            /// Returns a new EMF+ Metafile that contains just one drawing command:  a copy of the argument Image.
            /// But it is scaled to the specified width and height.
            /// </summary>
            /// <param name="image">The Image to be the sole object drawn in the Metafile.</param>
            /// <param name="width">The width to use for scaling.</param>
            /// <param name="height">The height to use for scaling.</param>
            public static Metafile NewMetafileContaining(Image image, int width, int height)
            {
                Metafile mf = NewMetafile(new MemoryStream(), width, height);
     
                using (Graphics gmf = Graphics.FromImage(mf))
                {
                    gmf.DrawImage(image, 0, 0);
                }
     
                return mf;
            }
     
     
            /// <summary>
            /// Writes the Image as an EMF+ formatted file to the specified stream.
            /// </summary>
            /// <param name="stream">The Stream (typically FileStream or MemoryStream) to write to.</param>
            /// <param name="image">The Image to write out as an EMF.</param>
            public static void WriteToEmfStream(Stream stream, Image image)
            {
                WriteToEmfStream(stream, image, image.Width, image.Height);
            }
     
     
            /// <summary>
            /// Writes the Image as an EMF+ formatted file to the specified stream,
            /// scaled to the specified width and height.
            /// </summary>
            /// <param name="stream">The Stream (typically FileStream or MemoryStream) to write to.</param>
            /// <param name="image">The Image to write out as an EMF.</param>
            /// <param name="width">The width to use for scaling.</param>
            /// <param name="height">The height to use for scaling.</param>
            public static void WriteToEmfStream(Stream stream, Image image, int width, int height)
            {
                using (Metafile mf = NewMetafile(stream, width, height))
                using (Graphics gmf = Graphics.FromImage(mf))
                {
                    gmf.DrawImage(image, 0, 0);
                }
            }
     
     
            /// <summary>
            /// Returns a new byte array holding the Image in EMF format,
            /// scaled down as necessary to the Graphics measurements of the default PrintDocument.
            /// </summary>
            /// <param name="image">The Image to be converted to bytes in EMF format.</param>
            public static byte[] SerializeToBytes(Image image)
            {
                byte[] buffer = null;
     
                if (image == null)
                {
                    buffer = new byte[1];
                    buffer[0] = 0;
                    // Two other options for the null case:
                    //buffer = new byte[0];
                    //return null;
                }
               else
                {
                    using (MemoryStream ms = new MemoryStream())
                    {
                        WriteToEmfStream(ms, image);
     
                        ms.Seek(0, SeekOrigin.Begin);
                        buffer = new byte[ms.Length];
                        ms.Read(buffer, 0, (int)ms.Length);
                    }
                }
     
                //if (image == null)
                //    System.Diagnostics.Debug.Print("SerializeToBytes: Image = null");
                //else
                //    System.Diagnostics.Debug.Print("SerializeToBytes: Image size = " + image.Size + " (size in bytes = " + buffer.Length.ToString() + ")");
     
                return buffer;
            }
     
     
            /// <summary>
            /// Returns a new Image created from the image file in the argument byte array.
            /// </summary>
            /// <param name="buffer">The byte array containing an image file to be converted into an Image.</param>
            public static Image DeserializeFromBytes(byte[] buffer)
            {
                // Handle all three potential representations of null Image
                if (buffer == null
                    || (buffer.Length == 0)
                    || (buffer.Length == 1 && buffer[0] == 0))
                    return null;
     
                Image image = null;
                try
                {
                    using (MemoryStream ms = new MemoryStream(buffer))
                    {
                        image = Image.FromStream(ms);
                    }
                }
                catch (Exception ex)
                {
                    //LOG.Error("Deserialization failed: " + ex.Message);
                    throw;
                }
     
                return image;
            }
     
     
            // Extract a sub-region from the provided raster bitmap.
            static public Bitmap ExtractImageRegion(Bitmap srcBitmap, Rectangle section)
            {
                if ((srcBitmap == null) || (section.Width == 0) || (section.Height == 0))
                {
                    return null;
                }
     
                // Create the new bitmap and associated graphics object
                Bitmap bmp = new Bitmap(section.Width, section.Height);
                bmp.SetResolution(srcBitmap.HorizontalResolution, srcBitmap.VerticalResolution);
     
                using (Graphics g = Graphics.FromImage(bmp))
               {
                    // Draw the specified section of the source bitmap to the new one
                    g.DrawImage(srcBitmap, 0, 0, section, GraphicsUnit.Pixel);
     
                }
     
                // Return the bitmap
                return bmp;
            }
     
            #endregion
     
     
            // Killed app and restarted profiler between test btns
            // grabbed mem snapshot before and after clicking button
           
            
            private void button1_Click(object sender, EventArgs e)
            {
                // Just load up a normal Image class
                // In Memory profiler I was seeing a 24byte managed chunk for the Bitmap class
                // and in the unmanaged memory two blocks allocated
                // Physical Memory|Private|Other Data|41,050 kb
                // and Physical Memory|Potentially Shared|Other Data| 10,016 kb
                testImg = Image.FromFile(test3Path);
            }
     
     
            private void button2_Click(object sender, EventArgs e)
            {
                // Load of a emf from an image
                // In Memory profiler I was seeing a 24byte managed chunk for the Metafile class
                // and in the unmanaged memory one block allocated
                // Physical Memory|Private|Unidentified Unmanaged Heaps|Data|10,039 kb
                // Nothing that looked like the big unexpanded memory
                Image tmp = Image.FromFile(test3Path);
                TestEmf = NewMetafileContaining(tmp);
                tmp.Dispose();
            }
     
            private void button3_Click(object sender, EventArgs e)
            {
                this.pictureBox1.Image = TestEmf;
            }
     
            private void button4_Click(object sender, EventArgs e)
            {
     
            }
     
            private void button5_Click(object sender, EventArgs e)
            {
                //this.testByteArray = new byte[5000];
     
            }
     
        }
    }


    Friday, March 22, 2013 5:19 PM
  • Much more info than I needed.  I was only considering a metafile stored on disk containing a raster image.  If the raster image is compressed, then the entire image has to be decompressed into memory to get any portion of the  image.

    Why are you using metafiles when you have raster images stored on disk?  You seem to want to minimize the memory usage.  If so, store the images as uncompressed bitmaps on disk and read only what you want into memory.

    Friday, March 22, 2013 5:34 PM

  • Why are you using metafiles when you have raster images stored on disk?  You seem to want to minimize the memory usage.  If so, store the images as uncompressed bitmaps on disk and read only what you want into memory.

    I need to load up those raster images into memory in such a way that they can be displayed by various .NET UI elements.  So, I need the whole image in memory.  Those UI elements work just as well with Metafiles as with Bitmaps.  If I load it up as a Bitmap, its 4-10x larger than if I load it up inside a Metafile.  So, my users tend to run out of memory much more quickly.  They don't like that.  And its hard to explain why two 5MB pictures from their cell phone consume 60MB plus on their laptop (which should be more powerful, not less powerful, than their cell phones).
    Friday, March 22, 2013 5:59 PM
  • If you have a compressed image, it can't be used until it's decompressed.  It doesn't matter where it's stored.  It seems to me that you would use less memory storing compressed images on disk rather than in memory in metafiles. 

    Why must the metafiles be memory based?  Why can't they be file based.

    • Edited by JohnWein Friday, March 22, 2013 6:29 PM
    Friday, March 22, 2013 6:17 PM
  • If you have a compressed image, it can't be used until it's decompressed.  It doesn't matter where it's stored.  It seems to me that you would use less memory storing compressed images on disk rather than in memory in metafiles. 

    Why must the metafiles be memory based?  Why can't they be file based.

    Actually, it appears that the GDI commands that get invoked when drawing the Metafile can work with the compressed images... it obviously uncompresses them in the drawing activity, but that's all in the graphics memory of the GDI graphics card that's tied to your screens.  And that memory is not at issue... it is tied up anyway.

    As far as I know, there's no way to get a PictureBox or any other .NET control to draw an Image to screen that is only stored in files... without loading the file into either a Bitmap (huge) or a Metafile (small) into memory.  I need to draw... so I need one or the other in memory.  Soooooo, the only way to minimize memory is by using a Metafile... cuz Bitmap is HUGE.

    Friday, March 22, 2013 6:42 PM
  • pictureBox1.Image = new Metafile(FileName)

    What's in memory?

    You have unusual codecs that use the GDI.  Most codecs are CPU based.

    Friday, March 22, 2013 7:01 PM
  • The same issue: http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/97904866-8806-464f-b1a9-bacb64506dcc  ??

    Ghost,
    Call me ghost for short, Thanks
    To get the better answer, it should be a better question.

    Sunday, March 24, 2013 9:28 AM
  • The same issue: http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/97904866-8806-464f-b1a9-bacb64506dcc  ??


    No, two different issues that are part of the same larger problem.  The larger problem:  if my users put large JPGs into my software (which we encourage them to do... drop in photos using their cell phones, for example), then over time my program will end up running out of memory... even though we are NOT leaking memory.  The problem is that we are fragmenting memory.  This used to happen very very fast... we'd pull in the JPG as a Bitmap which would explode its size 10x... we'd then serialize it to XML via IXmlSerializable which will fragment Large Object Heap like none other... then we compress it and chunk it to send it to our server (more LOH fragmentation).  Then all that gets released, but the fragmentation remains.

    We've greatly improved things by eliminating IXmlSerializable use (use ISerializable instead) and by rewriting our compression/chunking to stream everything, minimizing Large Object Heap fragmentation.  So, now you need more large pictures and a lot more time before running out of memory due to fragmentation.  But it still happens... part of the reason being unmanaged memory fragmenting due to the large pictures...

    We have rewritten our stuff to NOT hold the JPGs they are displaying as Bitmaps... we now hold them as Metafiles, requiring much less space.  We do that by using the answer to that other question you mention... just draw the portion(s) of the Metafile we need to show at any given point of time via DrawImage.

    HOWEVER, holding it as a Metafile doesn't help the fragmentation as much as we'd like if we first have to load it in as a Bitmap in order to draw it into a Metafile.  Each time we do that, we reserve a huge block of memory, write a small block after it, and then release the huge block.  Fragment.

    Soooooo, we are still very interested in a solution to this question, even though I got an answer to that other question.

    Sunday, March 24, 2013 1:33 PM
  • "We have rewritten our stuff to NOT hold the JPGs they are displaying as Bitmaps... we now hold them as Metafiles, requiring much less space"

    I don't get this result at all.  A JPEG held in a memory stream consumes much less memory than a metafile holding the same image.  Using the JPEG in a PictureBox is also more performant than using the metafile.

    The performance and memory might be able to be reduced farter using the JpegBitmapDecoder.


    • Edited by JohnWein Sunday, March 24, 2013 2:16 PM
    Sunday, March 24, 2013 2:15 PM
  • "We have rewritten our stuff to NOT hold the JPGs they are displaying as Bitmaps... we now hold them as Metafiles, requiring much less space"

    I don't get this result at all.  A JPEG held in a memory stream consumes much less memory than a metafile holding the same image.

    Much less memory?  Not true...maybe 24-48 bytes less... pretty irrelevant on a 5-12MB JPEG.

    I load a 10MB JPG file into a MemoryStream and it will consume 10MB of memory in Large Object Heap (plus a few bites of .NET bookkeeping).  It cannot do any less, right?

    I load that same 10MB JPG file with Image.FromFile() and it will consume a 10MB (+20 bytes) block in unmanaged memory (the compressed image) plus will consume another 42MB block in unmanaged memory for the uncompressed image that the Bitmap constructor creates from that JPG image.

    I then DrawImage that Bitmap into a new Metafile and dispose of that Bitmap.  I get a new 10MB block in a different part of unmanaged memory (a copy of the compressed image for the Metafile to use), and the old 10MB and 42MB blocks go away.

    So, in the end, my Metafile contains a single drawing command that tells it to draw the compressed JPG and it is just a few bytes larger than the 10MB compressed image.  There's probably a few more small objects on the Small Object Heap for the list of drawing commands and the one drawing command in that list, but those are inconsequential as .NET manages those just fine.

    If you don't believe the above John, just run the above code in a Memory Profiler... it is plain to see.

    Sooo, given the above is true, let me get back to my question here:

    Is there any way to get that JPG loaded into a Metafile as a drawing command WITHOUT first loading it into a Bitmap (with Image.FromFile() ) that creates the giant uncompressed pixel array???  

    (Even though I immediately release that memory, it temporarily requires that memory space and results in memory fragmentation, which together contributes to premature Out of Memory exceptions.  If I can get rid of all the other contributors, I can probably live with this one... but there's some that are hard to get rid of, so I am trying my best to get rid of all that I can, including this one.)

    Sunday, March 24, 2013 2:41 PM

  • Using the JPEG in a PictureBox is also more performant than using the metafile.


    If you set PictureBox.Image to a Metafile containing a JPEG, it holds onto the Metafile.

    If you set PictureBox.Image to a Bitmap containing a JPEG, it holds onto the Bitmap.

    So, either way, there is no less memory held by a PictureBox than by a Metafile.

    But the PictureBox WILL draw the Bitmap much faster than it draws the Metafile.  If that's what you mean by "more performant", then you are right.  But I am willing to pay those extra milliseconds to decode the JPEG in order to avoid consuming the extra memory.  My users will rarely notice those milliseconds (they typically won't have that many pictures being redrawn at once)... but my users are quite irritated when they run out of memory after a few hours or days of active use.

    Sunday, March 24, 2013 2:47 PM
  • "I load a 10MB JPG file into a MemoryStream and it will consume 10MB of memory in Large Object Heap (plus a few bites of .NET bookkeeping).  It cannot do any less, right?"

    This is correct.

    If you construct an image from that stream, it will temporarily decompress the image to its full size the same as if the image is held in a metafile.  But whatever compression the metafile uses, it is much less efficient than the JPEG compression.  I compared a 10240 X 7680 image in a JPEG memory stream and a metafile memory stream.  The JPEG stream was 6 MB and the metafile stream was 150 MB.  Loading the image into a PictureBox too 3X as long for the metafile as for the JPEG.

    Sunday, March 24, 2013 2:58 PM
  • "My users will rarely notice those milliseconds (they typically won't have that many pictures being redrawn at once)... but my users are quite irritated when they run out of memory after a few hours or days of active use."

    I get an Out of Memory error much quicker with the metafile than with the JPEG memory stream.  I'll minimize the size of the app I used and upload it.

    Sunday, March 24, 2013 3:02 PM
  • If you construct an image from that stream, it will temporarily decompress the image to its full size the same as if the image is held in a metafile.  But whatever compression the metafile uses, it is much less efficient than the JPEG compression.  I compared a 10240 X 7680 image in a JPEG memory stream and a metafile memory stream.  The JPEG stream was 6 MB and the metafile stream was 150 MB.  Loading the image into a PictureBox too 3X as long for the metafile as for the JPEG.

    How did you load up that Metafile?  Can you post the code you are using?

    Your numbers sound about right for a Bitmap loaded with a 10240x7680 image... a Bitmap doesn't have bad compression... it has no compression... it will hold the number of pixels times the size of each pixel.  So, if you have 16bits per pixel, a Bitmap will end up being 2*10240*7680 bytes (150MB).

    If you load up a Metafile with that Bitmap AFTER stripping off the JPEG, then the Metafile will have a command to draw that uncompressed Bitmap and will be the same size as the Bitmap.

    If you load up a Metafile with that Bitmap BEFORE stripping off the JPEG, then the Metafile will have a command to draw that compressed JPEG and will only consume about 6MB... not 150MB.

    If you don't believe me John, run the code above.

    Sunday, March 24, 2013 3:09 PM
  • "If you load up a Metafile with that Bitmap BEFORE stripping off the JPEG, then the Metafile will have a command to draw that compressed JPEG and will only consume about 6MB... not 150MB."

    Then the metafile serves no purpose.  It just adds a redundant layer to any GDI commands that use the JPEG.  We're just talking about the same GDI+ locks on the JPEG.

    Sunday, March 24, 2013 3:17 PM
  • "If you load up a Metafile with that Bitmap BEFORE stripping off the JPEG, then the Metafile will have a command to draw that compressed JPEG and will only consume about 6MB... not 150MB."

    Then the metafile serves no purpose.  It just adds a redundant layer to any GDI commands that use the JPEG.  We're just talking about the same GDI+ locks on the JPEG.

    For those few bytes, that layer certainly serves a purpose:  it makes it an Image object that I can put into a property or column of a DataTable and then I can databind to it with .NET controls that expect an Image... and I get all the Image functionality.  I can also add annotations on top of the JPEG as simply additional drawing commands in the Metafile and treat the whole as an Image.

    Sunday, March 24, 2013 3:56 PM
  • "For those few bytes, that layer certainly serves a purpose:  it makes it an Image object that I can put into a property or column of a DataTable and then I can databind to it with .NET controls that expect an Image... and I get all the Image functionality.  I can also add annotations on top of the JPEG as simply additional drawing commands in the Metafile and treat the whole as an Image."

    I don't see it.  You're adding complexity.  You can do what you want with Image.FromStream.

      MemoryStream MS;
      
    public Image GetJPEG()
      {
        if (MS != null) MS.Dispose();
        MS = 
    new MemoryStream(File.ReadAllBytes("Combined.jpg"));
        return Image.FromStream(MS);
      }

    Sunday, March 24, 2013 4:11 PM
  • I don't see it.  You're adding complexity.  You can do what you want with Image.FromStream.

      MemoryStream MS;
      
    public Image GetJPEG()
      {
        if (MS != null) MS.Dispose();
        MS = 
    new MemoryStream(File.ReadAllBytes("Combined.jpg"));
        return Image.FromStream(MS);
      }

    Uh, no... Image.FromStream above will construct a Bitmap... and that Bitmap will contain the UNCOMPRESSED pixel array.

    So, if I save into my database your JPG as just raw bytes, it will consume 6MB in the database.  Fine.  But then when I pull it back out and do Image.FromStream as above, it will consume 150MB of unmanaged memory.  The PictureBox that draws it will be fast, but lots of memory consumption.

    If instead I load your JPG into a Metafile and save the Metafile into an Image column of that same database, then when I pull it back out it comes out as a 6MB Metafile that I can assign to the PictureBox... it'll draw a little slower, but minimal memory consumption.  That huge uncompressed pixel array never gets generated (except to the screen, but that memory is allocated to the screen anyway).

    So, the only remaining issue is this:  when the user first asks me to load up their 6MB JPEG, I want to put it into a Metafile in the data-bound Image column in my DataSet that will show up in the PictureBox immediately and will be saved to the Image column in the database when they hit Save.  The only way I know to do that requires me to use Image.FromFile or Image.FromStream, giving me a Bitmap with that huge uncompressed pixel array... is there some other way to get it into a Metafile without incurring the memory cost/fragmentation of that uncompressed pixel array?

    Sunday, March 24, 2013 4:37 PM
  • "Uh, no... Image.FromStream above will construct a Bitmap... and that Bitmap will contain the UNCOMPRESSED pixel array."

    Your metafile does the same thing.

    The Uncompressed image exists only while the image is being drawn.

    Sunday, March 24, 2013 4:52 PM
  • "Uh, no... Image.FromStream above will construct a Bitmap... and that Bitmap will contain the UNCOMPRESSED pixel array."

    Your metafile does the same thing.

    The Uncompressed image exists only while the image is being drawn.

    Are you sure of that?  I cannot see any evidence of that in the memory profiler.  But then I can't take a snapshot in the middle of the image drawing (in the middle of a GDI command).  I would assume that the GDI commands would draw the JPG directly to the screen memory, without any need to generate the expanded pixel array in system memory.  But I can't see the implementation of the GDI commands.

    HOWEVER, if that is true, then while using a Metafile will reduce sustained memory consumption, it'll possibly horribly increase the memory fragmentation, since every screen refresh may result in consuming and then releasing another huge block of memory.

    Is there a tool that will show the layout of objects in memory so that you can see the memory fragmentation??  (Then I could look for that impact on fragmentation as evidence that Metafiles create giant pixel arrays when they draw... rather than just directly drawing to the screen memory.)

    Sunday, March 24, 2013 5:08 PM
  • "HOWEVER, if that is true, then while using a Metafile will reduce sustained memory consumption, it'll possibly horribly increase the memory fragmentation, since every screen refresh may result in consuming and then releasing another huge block of memory."

    The way you're using a metafile plays back the same drawing commands that you would get from using Image.FromStream.  It doesn't increase or decrease memory requirements appreciably nor should it significantly affect the time required.  If you find it convenient to use it, you should.  But expecting to gain speed or reduce memory by recording and playing back macros is equivalent to expecting to decrease the size of a compressed file by compressing it a second time.

    To use a compressed image for drawing, it has to be decompressed.  There is no way to find a single pixel in a standard (non-progressive) compressed jpeg without decompressing the entire image.

    Sunday, March 24, 2013 5:46 PM
  • "HOWEVER, if that is true, then while using a Metafile will reduce sustained memory consumption, it'll possibly horribly increase the memory fragmentation, since every screen refresh may result in consuming and then releasing another huge block of memory."

    The way you're using a metafile plays back the same drawing commands that you would get from using Image.FromStream.  It doesn't increase or decrease memory requirements appreciably nor should it significantly affect the time required.  

    Uhh, yeah, that I know is false.

    Image.FromStream on a JPEG does NOT create a sequence of drawing commands...  it creates a Bitmap.  Unlike a Metafile which is a sequence of drawing commands, a Bitmap is not... it's a pixel array.  There is no sequence of drawing commands stored in a Bitmap.

    A Bitmap with a 10240x7680 pixel array consumes a huge block of memory, no matter whether it was loaded from a 6MB JPEG or not.  And you can draw 18 BILLION drawing commands into that Bitmap without increasing its size one bit... it will just be that pixel array, modified by those drawing commands.

    A Metafile is just a series of drawing commands... and one of those commands can be "draw this JPEG"... in which case it'll just consume the memory for the JPEG... and one drawing command.  But if you draw 18 BILLION more drawing commands to it, it will grow to many GB in size... because it records each drawing command in a sequence.

    Sunday, March 24, 2013 6:13 PM

  • To use a compressed image for drawing, it has to be decompressed.  There is no way to find a single pixel in a standard (non-progressive) compressed jpeg without decompressing the entire image.

    Yes, it has to be decompressed... but that does NOT mean you have to build the whole pixel array in memory!  That's why JPEG and GIF and PNG and so on all work so well... because you can compress and decompress incrementally.

    There's plenty of software out there that can compute the value of a particular pixel from a JPEG without needing to construct the whole uncompressed bit array to do it.  Yes, it has to decompress the JPEG... but it can do that incrementally.  I believe GDI commands have that ability as well, but I don't know that for a fact.

    Note that DeflateStream and GZipStream does similar (though using the compression algorithm used for PNG, not the compression used for JPG)... both of those Streams can take that uncompressed image and compress it incrementally, chunk by chunk, never having the whole uncompressed or whole compressed image in memory at any point in time.  And it can then decompress it chunk by chunk, never having the whole compressed or whole uncompressed image in memory at any point in time.

    So, yes, it has to be decompressed... but no, there is absolutely no reason for it to build the whole pixel array in system memory.  It can start executing the drawing command incrementally as it decompresses the JPEG, chunk by chunk.  Any self-respecting graphics command would do so.  I assume GDI does so.
    Sunday, March 24, 2013 6:17 PM
  • Now you're getting ridiculous.  I wish you luck.
    Sunday, March 24, 2013 6:23 PM
  • The OP has changed this thread's subject so I will answer the current subject:  "Will loading a JPG into a Metafile consume less memory than loading it into a Bitmap"

    The answer is yes.

    I constructed a 10240 x 7680 image and saved it to a JPEG file.  I also constructed a metafile containing the JPEG and commands to draw the image.  I made two projects, one loading the JPEG into a PictureBox and the other loading the metafile into a PictureBox.  The image below shows the result:

    The program using the JPEG uses about 256 MB while the program using the metafile uses about 32 MB.

    A solution containing the projects with the files are on my SkyDrive in the "CompareJpgAndEmf.zip" file.

    • Marked as answer by TCC Developer Friday, March 29, 2013 4:07 AM
    Monday, March 25, 2013 3:45 PM
  • Image.FromStream without validation generally performs better than using a Metafile:

    using System.Windows.Forms;
    using System.IO;
    using System.Drawing;
    namespace CompareMetafileAndBitmap
    {
      
    public partial class Form1 : Form
      {
        
    public Form1()
        {
          InitializeComponent();
          
    FileStream fs = new FileStream(@"../../Combined.jpg"FileMode.Open);
          pb.Image =  
    Image.FromStream(fs, falsefalse);
          pb.Dock = 
    DockStyle.Fill;
          pb.SizeMode = 
    PictureBoxSizeMode.StretchImage;
          pb.Parent = 
    this;
          
    this.Text = "Image.FromStream";
        }
        
    PictureBox pb = new PictureBox();
      }
    }


    Friday, March 29, 2013 4:06 PM
  • Image.FromStream without validation generally performs better than using a Metafile:


    That depends upon what you mean by "performs better".  It will draw faster, yes.  But it will use more memory... potentially a LOT more memory.  For example, the Metafile might consume only 10MB where the Bitmap consumes over 100MB.  More often it is more like 5MB vs. 25MB.  But the biggest problem is the fragmentation if you do that a lot with very different sizes of things.

    Friday, March 29, 2013 4:22 PM
  • "That depends upon what you mean by "performs better"."

    Image.FromStream without validation uses less memory than an equivalent Metafile and renders slightly more quickly because the data isn't fragmented within the file.  The Metafile just adds a layer of complexity on top of the Image that Image.FromStream without validation doesn't.  There is no fragmentation when either one is rendered since unmanaged memory is used to draw the image.

    In my example Image.FromStream uses 24 MB while the Metafile uses 32 MB and the Bitmap uses 256 MB.

      
    Friday, March 29, 2013 4:40 PM