4

I'm tracking down a pesky problem and I've narrowed down the problem down and realized it only happens when I'm dealing with an Image instance returned by Image.FromStream(). I have a utility method that returns an Image instance from a file using a Stream so I don't have the file handle left open. Here is that utility method (nothing special):

public static Image ImageFromFileReleaseHandle(string filename)
{
    using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
    {
        return Image.FromStream(fs);
    }
}

When I try to save an Image loaded from the above method I'm getting an InteropServices.ExternalException "A generic error occurred in GDI+.". The following code example will demonstrate this:

private void button6_Click(object sender, EventArgs e)
{
    var filename = @"D:\My Documents\My Pictures\2010-03-27 hangover hike.jpg";

    //  Get an Image instance
    Image image;
    using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
    {
        image = Image.FromStream(fs);
    }

    //  Save to a temp file - this is the code that throws the exception
    image.Save(Path.GetTempFileName());
}

If I load the image using Image.FromFile() I can save no problem:

private void button6_Click(object sender, EventArgs e)
{
    var filename = @"D:\My Documents\My Pictures\2010-03-27 hangover hike.jpg";

    //  Get an Image instance
    Image image = Image.FromFile(filename);

    //  Save to a temp file - this is the code that throws the exception
    image.Save(Path.GetTempFileName());
}

I can't think of any additional information that would be helpful. Hopefully my code examples are simple enough that you can clearly see the problem.

Thanks, Steve

scubasteve
  • 2,718
  • 4
  • 38
  • 49

4 Answers4

3

According to the documentation:

You must keep the stream open for the lifetime of the Image.

Try calling Save inside the using block to verify that no exception is thrown if the stream is still open.

Jeff Ogata
  • 56,645
  • 19
  • 114
  • 127
1

try this instead

//  Get an Image instance
    Image image;
    using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
    {
        image = Image.FromStream(fs);
        //  Save to a temp file - this is the code that throws the exception
        image.Save(Path.GetTempFileName());
    }

I think the using block is giving you issues

msarchet
  • 15,104
  • 2
  • 43
  • 66
  • Thanks for the suggestion and you are correct that leaving the stream open will allow me to save. However this won't work in my application because the code that creates the Image instance and the code the saves it aren't related. I don't want to leave a FileStream open because there is no guarantee it will be closed. What I need is a pure memory Image instance with NO dependencies to stream or files. – scubasteve Oct 17 '10 at 22:54
  • @user47: Then copy the image from your FileStream into a MemoryStream. – Allon Guralnek Oct 17 '10 at 23:02
  • @Allon: I thought the same thing after I left my last comment and just did an experiment and it does seem to work. I now need to research the implications of leaving a MemoryStream open... Thanks for the suggestion – scubasteve Oct 17 '10 at 23:05
  • @SteveK: MemoryStream is a completely managed object, so there are no implications. See [Jon Skeet's answer](http://stackoverflow.com/questions/234059/is-a-memory-leak-created-if-a-memorystream-in-net-is-not-closed/234257#234257) about it. Also, you could always create a class `MemoryImage` which could contain both the `Image` and the `MemoryStream` and its `Dispose` method would dispose of both, cleaning up everything. – Allon Guralnek Oct 18 '10 at 08:58
0

I got the same problem but instead of using a FileStream, I only have a MemoryStream, because I'm downloading the image from a database. When I save the image for the first time in the database everything is fine but when I execute an update of the image in the database throws an ExternalException. Here's my code if you want to analyze.

public class ImageConverter
{
    public static byte[] imageToByteArray(Image imageIn)
    {
        if (imageIn == null)
            return new byte[] { 45 };

        using (MemoryStream ms = new MemoryStream())
        {
            imageIn.Save(ms, imageIn.RawFormat);
            return ms.ToArray();
        }
    }

    public static Image byteArrayToImage(byte[] byteArrayIn)
    {
        if (byteArrayIn == null)
            return null;

        using (MemoryStream ms = new MemoryStream(byteArrayIn))
        {
            Image returnImage = Image.FromStream(ms);
            return returnImage;
        }
    }

} 
Adi Lester
  • 24,731
  • 12
  • 95
  • 110
0

Based on suggestions and comments so far and my own additional reading I think I have a working solution. Basically the problem is that I can't close the Stream. Fine, but I also can't leave a FileStream open with no guarantee it will be closed properly.

Instead I'm reading the file into a buffer then creating a MemoryStream and passing that to Image.FromStream().

var filename = @"D:\My Documents\My Pictures\2010-03-27 hangover hike.jpg";

byte[] imageData = FileUtils.ReadWholeFileBytes(filename);
Image image = Image.FromStream(new MemoryStream(imageData));

//  dump the buffer to test if this upsets the Memory Stream.  I need to read more
//  about MemoryStream and how it may keep the GC from cleaning up it's underlying data
imageData = null;        

//  Save to a temp file
image.Save(Path.GetTempFileName());

Thanks all for your suggestions and questions.

scubasteve
  • 2,718
  • 4
  • 38
  • 49
  • UPDATE: My solution is only a half-solution. It has the unfortunate consequence of resulting in an Image with an ImageFormat set to MemoryBmp. This means that you will need to explicitly set the ImageFormat when saving or else it will be saved with the PNG encoder. – scubasteve Oct 18 '10 at 23:46