none
Can I load a JPG into a Metafile without first expanding it into a huge uncompressed Bitmap? RRS feed

  • Question

  • If you want to inquire about why I want to load a JPG into a Metafile, 

    or debate whether there is any value in doing so, please discuss that in:

    http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/d9fc16f9-0357-41ff-9fff-2327aacab6f9/

    In this thread, please just assume I have good reason to load a JPG into a Metafile.

    The question in this thread is simply:

    Is there a way to Draw a JPG into a Metafile without first uncompressing it into a Bitmap??

    The only way I currently know to do it is to do Image.FromFile or Image.FromStream on a JPEG-encoded file which produces a Bitmap.  And then you can create a new Metafile and get a Graphics from that Metafile and do graphics.DrawImage(thatBitmap) to draw that JPEG into the Metafile.  The Metafile will only hold the compressed JPEG.  Great.  But to do it, I had to create that Bitmap which will temporarily consume a giant block of memory (the uncompressed pixel array), which will fragment memory, and contribute to premature Out of Memory Exceptions.  Hence, I'd rather avoid consuming that block of memory if possible.

    Sunday, March 24, 2013 6:50 PM

Answers

  • The best answer to this thread came out in another thread (thanks to JohnWein)... but in case someone just finds this simpler thread, I thought I'd post the answer here as well.  The "trick" in the following code is realizing that if you pass "false" in as the third parameter to Image.FromStream that, in addition to not validating, it also does not generate the uncompressed pixel array in memory... but is otherwise usable.

            /// <summary>
            /// Returns a new EMF+ Metafile that contains just one drawing command:  an image loaded from filestram using the argument path.
            /// </summary>
            /// <param name="imageFilePath">The file path to the Image to be the sole object drawn in the Metafile.</param>
            public static Metafile NewMetafileContaining(string imageFilePath)
            {
                Metafile mf = NewMetafile();
                using (FileStream fs = new FileStream(imageFilePath, FileMode.Open))
                using (Image image = Image.FromStream(fs, false, false)) // false's avoid loading pixel array
                using (Graphics gmf = Graphics.FromImage(mf))
                {
                    gmf.DrawImage(image, new Rectangle(0, 0, image.Width, image.Height));
                }
                return mf;
            }

    where we use this function to create a new Metafile ready to be drawn into:

            /// <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);
                }
            }
    

    In all our testing so far, that seems to do what we need!

    NOTE:  If you don't need the Image in-memory, then rather than sticking it in a Metafile, you can just leave it on-disk and use the Image returned by Image.FromStream directly... thereby using even less memory than the Metafile (and it may even be faster than the Metafile per JohnWein's testing).  In our case, its a win to have it in a Metafile for a variety of reasons.

    Thanks to all... but particularly to JohnWein for pointing out the behavior of the third parameter of Image.FromStream.

    • Marked as answer by TCC Developer Friday, April 5, 2013 7:33 AM
    Friday, April 5, 2013 7:33 AM

All replies

  • This question was answered in the linked thread.  Constructing an image from a memory stream and a metafile from that image doesn't decode the image.
    Sunday, March 24, 2013 11:12 PM
  • Hi Tcc,

    In my opinion, this thread is similar with the other one, would you mind me to merge them?

    And JohnWein had posted new solution.

    Best regards,


    Mike Feng
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.


    Tuesday, March 26, 2013 5:00 AM
    Moderator
  • In my opinion, this thread is similar with the other one, would you mind me to merge them?

    I would rather not merge them as this question is very important to me, has not yet gotten an answer, and this question will get lost completely in that other thread.

    My response to John Wein here might make the distinction more clear...

    Tuesday, March 26, 2013 5:43 PM
  • This question was answered in the linked thread.  Constructing an image from a memory stream and a metafile from that image doesn't decode the image.

    Image.FromStream and Image.FromFile both construct a Bitmap if given JPEG content... that Bitmap WILL allocate memory for the uncompressed pixel array... as best I can tell, you can never have a Bitmap without having the full uncompressed pixel array.   You can then DrawImage into the Metafile and be able to release that memory for the uncompressed pixel array... but that memory gets allocated before you get to that step... and that will fragment unmanaged memory.  Check it in a Memory Profiler if you don't believe me.

    So, no, that is NOT an answer to my query.  I know about that way... I'm looking for a different way that does NOT involve a Bitmap object.

    Just to be clear, if I execute this code:

    Metafile mf = new Metafile(...);
    using (FileStream fs = new FileStream("3.jpg", FileOptions.Open, FileAccess.Read))
    using (Image image = Image.FromStream(fs))
    using (Graphics g = Graphics.FromImage(mf))
    {
    	g.DrawImage(image);
    }

    and then break before the DrawImage step, you can see that the Image in image is a Bitmap object and that it is holding memory for both the uncompressed pixel array plus for the FileStream with the compressed JPG data.  The JPG data may not be in memory, but that's not the concern.

    If you then break after the code, you'll see that the uncompressed pixel array is gone... the Metafile in mf that remains has memory holding the compressed JPEG data, which tells it how to draw whatever is pictured there.

    My goal is to construct that Metafile containing compressed JPEG data WITHOUT ever allocating memory for the uncompressed pixel array (which means without constructing a Bitmap)... given my end state is that I just need the compressed JPEG data in the file to get inserted into the Metafile... the uncompressed pixel array is never used for anything.


    Tuesday, March 26, 2013 5:52 PM
  • I showed the use of a metafile containing a compressed jpg in the referenced thread.  How is it not what you are looking for?
    Tuesday, March 26, 2013 6:03 PM
  • I showed the use of a metafile containing a compressed jpg in the referenced thread.  How is it not what you are looking for?

    I started the thread saying that I *know* that I can put an uncompressed JPEG into a Metafile... being able to do that is NOT the question.

    The question is how to get the JPG into the Metafile without constructing a temporary Bitmap and drawing that Bitmap into the Metafile.  Why do I want to avoid the temporary Bitmap?  Because the Bitmap will allocate a gigantic uncompressed pixel array... which, although it will be immediately released, it will tend to fragment memory... and I am fighting memory fragmentation issues.

    Your code uses Image.FromStream on the JPEG to build the Metafile... Image.FromStream will build that temporary Bitmap that I am trying to avoid.  Yes, your allocated memory is small AFTER you've disposed that Bitmap... but you may have a big fragmentation problem if you start doing that a lot.

    Tuesday, March 26, 2013 6:44 PM
  • "Your code uses Image.FromStream on the JPEG to build the Metafile... Image.FromStream will build that temporary Bitmap that I am trying to avoid."

    Where did you find this code?  I only loaded a metafile that I constructed without showing how it was constructed.  I answered that thread's changed subject.  Obviously you can construct a metafile as I did by writing and reading bytes without constructing anything else.


    • Edited by JohnWein Tuesday, March 26, 2013 7:13 PM
    Tuesday, March 26, 2013 7:04 PM
  • "Your code uses Image.FromStream on the JPEG to build the Metafile... Image.FromStream will build that temporary Bitmap that I am trying to avoid."

    Where did you find this code?  I only loaded a metafile that I constructed without showing how it was constructed.

    LOL... if you didn't show how you loaded the JPG into the Metafile, then OBVIOUSLY you didn't answer my question on how you load a JPG into a Metafile without creating a temporary Bitmap!

    You suggested more than once in that thread and once in this thread that Image.FromStream is the answer... so, I assumed that was the suggestion that you were implying was your answer to my question.  If that is NOT your proposed answer, then you haven't proposed an answer as best I can tell.

    Here's the scenario that I am trying to figure out how to code:  the user says "use this JPG" and I want to load it directly into a Metafile that I can then save to my database and/or use with any code that takes an Image, but WITHOUT ever generating the uncompressed pixel array.

    Tuesday, March 26, 2013 7:31 PM
  • This is the question:  "Will loading a JPG into a Metafile consume less memory than loading it into a Bitmap?"

    Loading the 6,139,102 byte JPEG I used into a Metafile consumed 6,142,580 bytes and loading it into a Bitmap consumes  235,929,654 bytes.

    How have I not answered it? 

    the user says "use this JPG" and I want to load it directly into a Metafile that I can then save to my database and/or use with any code that takes an Image, but WITHOUT ever generating the uncompressed pixel array.

    You write it to a byte[] along with the metafile headers and records.  Save the byte[] to your database.  Writing and reading bytes doesn't generate anything.

    Tuesday, March 26, 2013 7:56 PM
  • ... and I am fighting memory fragmentation issues.

    ...

    That makes this particular piece of the puzzle sound like a Band-Aid solution... are you sure that there are not other, more pressing memory concerns somewhere else in your application?  I suspect that there are, especially given that you performed some refactoring on other parts of the code that helped the issue significantly, even though the related code should not have caused exceptions to begin with (sure, XML serialization has lots of bloat so is slower and needs more memory than binary serialization, but it should just take longer, not cause a crash).  There's probably some more cleanup/refactoring to do somewhere else in the code.  Do you use any unmanaged code directly in the application?  Like Windows API calls, or 3rd party unmanaged DLLs?

    There are an awful lot of image handling programs out there, from album and slideshow programs to full image editors like Paint.Net.  If you are working with the framework correctly, you should not have to worry about memory fragmentation or experience out of memory exceptions (with reasonable code... you obviously would not declare Long[] x = new long[Int32.MaxValue - 1];).  Come to think of it, I have a Web Forms application that does lots of dynamic image generation and runs for months at a time between restarts without ever causing an exception.  There's also an associated Forms app that generates and serves images from CAD files, and it also runs for months at a time without issue.

    Given all of the detail you've provided about your experience, it really sounds like some other underlying issue for which large image generation is simply a convenient trigger.


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Tuesday, March 26, 2013 7:57 PM
    Moderator
  • This is the question:  "Will loading a JPG into a Metafile consume less memory than loading it into a Bitmap?"


    NO!  That is the question in that OTHER thread... please do NOT turn this thread into that one.

    The question here is:  

    Can I load a JPG into a Metafile without first expanding it into a huge uncompressed Bitmap?

    (yikes, sorry, I didn't expect that to come out gigantic like that)
    Tuesday, March 26, 2013 9:05 PM
  • The question here is:  

    Can I load a JPG into a Metafile without first expanding it into a huge uncompressed Bitmap?

    What is the answer to the other thread:

    My last response answers this thread.

    Tuesday, March 26, 2013 9:30 PM
  • Reed, we are having Out of Memory Exceptions that are NOT due to running out of free space and are NOT due to memory leaks (assuming we can trust two of the top memory profilers).  That points to memory fragmentation (which those two memory profilers give very little info on).

    Memory fragmentation is not a "single culprit" thing... its basically about minimizing the number of large temporary objects that you generate.

    Our list of large objects was:
    (1)  strings generated during XML serialization (eliminated)
    (2)  byte[]'s generated during serialization (reduced dramatically)
    (3)  byte[]'s generated during compression (eliminated)
    (4)  byte[]'s generated during chunking in web service calls (reduced 25% and normalized to reduce fragmentation)
    (5)  Bitmaps with huge uncompressed pixel arrays (reduced dramatically)
    (6)  Metafiles containing compressed JPGs or PNGs

    The latter two show up directly as themselves, and then show up again in all the rest (because they are in DataSets that get serialized, compressed, and sent in chunks to our web service calls).

    So, reducing the sizes of images impacts the sizes of all the rest, and thus will reduce dramatically the fragmentation issues.  However, you are right that there are other places in our code we can look to reduce fragmentation, and we have been.  A lot of those have been easier to fix than the Images... for which finding info has proven elusive.  But some seem to be difficult / impossible to fix... but reducing the size of images would help.  We can do that several ways... but the easiest way would be finding an answer to the question that I asked at the top of this thread (putting JPGs into Metafiles without constructing Bitmaps).
    Tuesday, March 26, 2013 10:46 PM
  • Hi TCC Developer,

    Based on my understanding, JPEG is compressed from the bitmap, for detailed, you can refer here: http://en.wikipedia.org/wiki/JPEG 

    So when convert a raster graphic to another raster graphics, there may have some formulas can convert the compressed matrix.

    But now, the metafile is a kind of vector graphics, so it needs the original picture to make vector. Even though the bitmap is not only option, but there should be an equality as a bridge between raster graphics and vector graphics.

    Best regards,


    Mike Feng
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Wednesday, March 27, 2013 12:03 PM
    Moderator
  • Hi Mike,

    Although Metafile is positioned as "vector graphics", it is really simply a sequence of GDI commands.  As such, it is capable of vector graphics in that it can record DrawLine and DrawCircle and such commands.  But it is ALSO capable of raster graphics in that it can record DrawImage(someBitmap) commands.  So that is your "equality as a bridge".

    So, which is better for raster graphics, Metafile or Bitmap?

    Bitmap is faster than Metafile for raster commands, but consumes more memory than Metafile (the Metafile can hold raster images compressed whereas the Bitmap always expands out the uncompressed pixel array.  So, in our case, we can afford to be a little slower to avoid excess memory usage... and fragmentation.

    For small resolution raster graphics, Bitmap is surely better.  But as the resolution grows, the memory consumed grows squared... and the advantage of Metafile over Bitmap grows, eventually making Metafile pretty compelling... particularly considering .NET's poor handling of Large Object Heap and unmanaged memory vs. its handling of Small Object Heap.

    Cheers,

        Brian

    Friday, March 29, 2013 3:57 AM
  • The best answer I have come up with so far to:

        Is there a way to Draw a JPG into a Metafile without first uncompressing it into a Bitmap??

    is "No, but Yes sorta...":

    Create a tiny separate .exe that I can invoke that simply reads in any given raster file into a Bitmap and then opens a new Metafile file on disk and does DrawImage of that Bitmap into that Metafile and quits, releasing all the Bitmap memory and fragmentation caused.

    Then in my main app, when the user points me to a raster file, rather than opening it and generating memory fragmentation in my main app, I can invoke that .exe to create a temporary file containing a Metafile, and then in my main app I can read in that file directly to a Metafile.  Clunky, but no memory impact on my main app.

    Any better ideas?
    • Marked as answer by TCC Developer Friday, March 29, 2013 1:37 PM
    • Unmarked as answer by TCC Developer Friday, April 5, 2013 7:33 AM
    Friday, March 29, 2013 1:35 PM
  • Or you could do in the way I suggested in the first thread where I showed you how to store a JPEG in a Metafile.  Study the metafile and write the compressed JPEG and associated drawing records.  It's actually quite simple as there are very few records required to draw the image.
    Friday, March 29, 2013 1:57 PM
  • Or you could do in the way I suggested in the first thread where I showed you how to store a JPEG in a Metafile.  Study the metafile and write the compressed JPEG and associated drawing records.  It's actually quite simple as there are very few records required to draw the image.

    So, you are suggesting that I reverse engineer the .EMF format and then just manually write the equivalent thing from the raster file that the user points me to?

    Yeah, I suppose that's an alternative solution... though hopefully I can find some .EMF spec that tells me what to write, rather than trying to reverse engineer it from a few example files.

    Friday, March 29, 2013 2:02 PM
  • "Or you could do in the way I suggested in the first thread where I showed you how to store a JPEG in a Metafile." 

    "examine the metafile with a binary reader or play it back by enumerating the records."

    You seem to need help reading and writing binary files.  Perhaps you could find another coder to assist.

    Friday, March 29, 2013 2:09 PM
  • You seem to need help reading and writing binary files.  Perhaps you could find another coder to assist.

    No, I am just skeptical that the reverse engineering of the binary format will be the best way to go... finding the spec seems more promising.  Here's a quote from Wikipedia:

    "some alternative implementations resorted to reverse engineering to figure out the file format from existing WMF files, which was difficult and error prone.[3]"  So, I don't think I am the only one who might prefer to use a spec.



    Friday, March 29, 2013 2:17 PM
  • "use a spec" and an example metafile.  The example metafile tells you what records you need to write and the [MS-EMFPLUS].pdf tells you how to write the records.  It's essentially the same as the process involved in editing a metafile as covered in the MSDN documentation.
    Friday, March 29, 2013 2:26 PM
  • The best answer to this thread came out in another thread (thanks to JohnWein)... but in case someone just finds this simpler thread, I thought I'd post the answer here as well.  The "trick" in the following code is realizing that if you pass "false" in as the third parameter to Image.FromStream that, in addition to not validating, it also does not generate the uncompressed pixel array in memory... but is otherwise usable.

            /// <summary>
            /// Returns a new EMF+ Metafile that contains just one drawing command:  an image loaded from filestram using the argument path.
            /// </summary>
            /// <param name="imageFilePath">The file path to the Image to be the sole object drawn in the Metafile.</param>
            public static Metafile NewMetafileContaining(string imageFilePath)
            {
                Metafile mf = NewMetafile();
                using (FileStream fs = new FileStream(imageFilePath, FileMode.Open))
                using (Image image = Image.FromStream(fs, false, false)) // false's avoid loading pixel array
                using (Graphics gmf = Graphics.FromImage(mf))
                {
                    gmf.DrawImage(image, new Rectangle(0, 0, image.Width, image.Height));
                }
                return mf;
            }

    where we use this function to create a new Metafile ready to be drawn into:

            /// <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);
                }
            }
    

    In all our testing so far, that seems to do what we need!

    NOTE:  If you don't need the Image in-memory, then rather than sticking it in a Metafile, you can just leave it on-disk and use the Image returned by Image.FromStream directly... thereby using even less memory than the Metafile (and it may even be faster than the Metafile per JohnWein's testing).  In our case, its a win to have it in a Metafile for a variety of reasons.

    Thanks to all... but particularly to JohnWein for pointing out the behavior of the third parameter of Image.FromStream.

    • Marked as answer by TCC Developer Friday, April 5, 2013 7:33 AM
    Friday, April 5, 2013 7:33 AM