9

I have this JPEG image, that opens fine in Picasa, Photoshop, web browser, etc., but in .NET it just refuses to work.

 Image image = Image.FromFile(@"myimage.jpg");
 image.Save(@"myimage2.jpg");
 // ExternalException - A generic error occurred in GDI+.

Is there a way to recover it in .NET so I can work with it (I need to resize it), without fixing the problem at the source?

Full exception details:

source: System.Drawing
type: System.Runtime.InteropServices.ExternalException
message: A generic error occurred in GDI+.
stack trace:
at System.Drawing.Image.Save(String filename, ImageCodecInfo encoder, EncoderParameters encoderParams)
   at System.Drawing.Image.Save(String filename, ImageFormat format)
   at System.Drawing.Image.Save(String filename)
   at ConsoleApplication20.Program.Main(String[] args) in C:\Users\sam\Desktop\S
ource\ConsoleApplication20\ConsoleApplication20\Program.cs:line 16

This issue is reproducible on Windows 7.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Sam Saffron
  • 128,308
  • 78
  • 326
  • 506
  • 2
    @Sam Saffron: You say "without fixing the problem at the source", does that mean that the image doesn't follow the standard for JPEG? If so, you can't expect .NET to handle images that don't follow the standard, even if other apps are more lenient. Also, have you checked the InnerException property? There might be more information there about why exactly it failed to load. – casperOne Feb 26 '10 at 08:32
  • It loads, it just does not save. I know the correct thing to do is run a robot over all thetvdb images and fix it, but that is out of my control, and this thing took me 30 minutes to find. All I get is a GDI error, with no more info or inner exception and explorer seems to have no trouble opening it, which is even weirder. – Sam Saffron Feb 26 '10 at 08:42
  • I just tried the same code in VB and it works with your particular image. Has it changed in anyway during your upload? – Paul Farry Feb 26 '10 at 09:07
  • I just retested and checked the md5 hash its the same file, perhaps this is an X64 issue (my os is Windows 7 X64) – Sam Saffron Feb 26 '10 at 09:21
  • tested with x32 target and it still failed, really odd – Sam Saffron Feb 26 '10 at 09:23
  • I worked around this by saving as a PNG, somehow .net allowed me to do that. – Sam Saffron Feb 26 '10 at 09:37
  • Same like Paul, I tried in C# with both VS2005 and 2008, and both work too. – Fadrian Sudaman Feb 28 '10 at 09:10
  • @Sam: I'm surprised you haven't posted the complete exception (including InnerException) using ex.ToString(). Please do that. – John Saunders Feb 28 '10 at 09:37
  • John, all you get is ExternalException - A generic error occurred in GDI+. – Sam Saffron Feb 28 '10 at 11:29
  • All, Darin seems to have gotten the error, so its not the crack speaking, something is broken somewhere. – Sam Saffron Feb 28 '10 at 11:53
  • @Sam: I'd still love to see the `ex.ToString()`. Call me old-fashioned, but stack traces can be useful, even if there's no `InnerException`. – John Saunders Feb 28 '10 at 18:29
  • @Sam: I don't see the exception there. Is that what you were pointing out? – John Saunders Feb 28 '10 at 22:48
  • @John I added full diagnostics for you. As you can see, its not that helpful – Sam Saffron Feb 28 '10 at 23:06
  • Since this seems to be some "Fox" show screenshot, can this (inability to save the pic) be due to copyright reasons? :)) That Windows 7 is apparently so smart.. – mlvljr Mar 05 '10 at 23:44

8 Answers8

12

This seems to work:

    using (Image image = Image.FromFile(@"c:\dump\myimage.jpg"))
    using (Image clone = new Bitmap(image))
    {
        clone.Save(@"c:\dump\myimage2.jpg", ImageFormat.Jpeg);
    }

image is actually a Bitmap anyway, so it should be similar. Oddly myimage2 is 5k smaller - the joys of jpeg ;-p

A nice thing about this is that you can resize at the same time (your actual intent):

    using (Image image = Image.FromFile(@"c:\dump\myimage.jpg"))
    using (Image clone = new Bitmap(image,
        new Size(image.Size.Width / 2, image.Size.Height / 2)))
    {

        clone.Save(@"c:\dump\myimage2.jpg", ImageFormat.Jpeg);
    }
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • I think there is a way to specify how much compress the jpeg, so my guess is that its loosing a tad of fidelity, nonetheless this workaround does work. I checked this in yesterday http://code.google.com/p/videobrowser/source/diff?spec=svn0944fd56b5b6aaee6ba4a82593c286d1361cc280&r=0944fd56b5b6aaee6ba4a82593c286d1361cc280&format=side&path=/MediaBrowser/Library/ImageManagement/ImageCache.cs – Sam Saffron Feb 28 '10 at 11:33
  • I guess I could fit in your fix, still do you think this is a legit bug in .net? Did you also get the error? – Sam Saffron Feb 28 '10 at 11:34
  • @Sam - yes I got the error with the original code, and yes I think this is a GDI bug. And no, I wouldn't expect it to be fixed any time soon. – Marc Gravell Feb 28 '10 at 22:31
  • I was tempted to log a call with MS, but after just spending half an hour talking to some monkey about another legit issue I do not think I could be bothered. – Sam Saffron Feb 28 '10 at 22:34
4

Try explicitly specifying the format:

using (Image image = Image.FromFile(@"test.jpg"))
{
    image.Save(@"myimage2.gif", ImageFormat.Gif);
}

All ImageFormat.Png, ImageFormat.Bmp and ImageFormat.Gif work fine. ImageFormat.Jpeg throws an exception.

The original image is in the JFIF format as it starts with FF D8 FF E0 bytes.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • yerp that was my workaround, I think it is fair from trial and error the condition of having problem jpegs is kind of rare, so switching formats is trivial and not that expensive. – Sam Saffron Feb 28 '10 at 11:37
  • so is JFIF not fully supported in .Net? – Sam Saffron Feb 28 '10 at 11:43
  • @Sam Saffron: JFIF is the actual file format used for JPEG-encoded images. It is supported by GDI+, and therefore supported by the .NET `Image` and related classes, which are just wrappers around GDI+. – Aaronaught Feb 28 '10 at 18:38
4

This is a long-standing bug in the .NET framework itself that I don't expect to see fixed any time soon. There are several related bugs I've also come across that throw the 'generic error occurred in GDI+' including if you access the PropertyItem collection for some JPEGs (to examine the EXIF codes) then from that point on you won't be able to save the image. Also, for no rhyme or reason some JPEGs, like the one you have here, simply won't save at all. Note that just saving as a different format won't always work either, although it does in this case.

The workaround I've employed in an app of mine that consumes pictures from all over the web (and therefore sees more than its fair share of these types of problems) is to catch the ExternalException and copy the image into a new Bitmap as in one of the previous answers, however simply saving this as a new JPEG will drop the quality rather a lot so I use code much like that below to keep the quality high:

namespace ImageConversionTest
{
    using System.Drawing;
    using System.Runtime.InteropServices;
    using System.Drawing.Imaging;
    using System.Globalization;

    class Program
    {
        static void Main( string[] args )
        {
            using( Image im = Image.FromFile( @"C:\20128X.jpg" ) )
            {
                string saveAs = @"C:\output.jpg";

                EncoderParameters encoderParams = null;
                ImageCodecInfo codec = GetEncoderInfo( "image/jpeg" );
                if( codec != null )
                {
                    int quality = 100; // highest quality
                    EncoderParameter qualityParam = new EncoderParameter( 
                        System.Drawing.Imaging.Encoder.Quality, quality );
                    encoderParams = new EncoderParameters( 1 );
                    encoderParams.Param[0] = qualityParam;
                }

                try
                {
                    if( encoderParams != null )
                    {
                        im.Save( saveAs, codec, encoderParams );
                    }
                    else
                    {
                        im.Save( saveAs, ImageFormat.Jpeg );
                    }
                }
                catch( ExternalException )
                {
                    // copy and save separately
                    using( Image temp = new Bitmap( im ) )
                    {
                        if( encoderParams != null )
                        {
                            temp.Save( saveAs, codec, encoderParams );
                        }
                        else
                        {
                            temp.Save( saveAs, ImageFormat.Jpeg );
                        }
                    }
                }
            }
        }

        private static ImageCodecInfo GetEncoderInfo( string mimeType )
        {
            // Get image codecs for all image formats
            ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();

            // Find the correct image codec
            foreach( ImageCodecInfo codec in codecs )
            {
                if( string.Compare( codec.MimeType, mimeType, true, CultureInfo.InvariantCulture ) == 0 )
                {
                    return codec;
                }
            }
            return null;
        }
    }
}

Note that you'll lose the EXIF information but I'll leave that for now (you'll generally still be able to read the EXIF codes, even when saving the source image fails). I have long since given up attempting to figure out what it is that .NET doesn't like about particular images (and have samples of various failure cases should anybody ever want to take up the challenge) but the approach above works, which is nice.

John Conners
  • 189
  • 1
  • 4
3

It seems to work fine on Windows XP and Vista, but not Windows 7.

I was able to find this issue on Microsoft Connect. It's not identical to your issue but it looks very similar - an ExternalException occurring when trying to resave a JPEG file. Currently at 16 repros including some comments that the issue still exists in the final release.

So it looks not to be a bug in the .NET Framework, but a bug in Windows 7 - specifically, a bug in the GdipSaveImageToStream API.

The workaround, as others have mentioned, is to force a conversion to another format. That's what both Marc and Darin's answers are doing. There is obviously some "extra" information in the JPEG file that is triggering the bug in Win7. When you convert to a different format or make a bitmap copy, that information (EXIF maybe?) is eliminated.

Aaronaught
  • 120,909
  • 25
  • 266
  • 342
  • I have a suspicion that it's in the headers somewhere, but I can't test it right this second. I suggest that someone try using jhead to strip various headers from the image file, and see if the code in the OP succeeds without one or more of the headers. The tool is here: http://www.sentex.net/~mwandel/jhead/ – Aaronaught Feb 28 '10 at 18:35
  • This is the most complete answer to my question now considering posting this to MS – Sam Saffron Feb 28 '10 at 21:58
2

Try using WPF's BitmapSource instead of the WinForm's Image, it supports more pixel, image and file formats.

Danny Varod
  • 17,324
  • 5
  • 69
  • 111
2

I tried yesterday on 32-bit Windows XP and can't reproduce the problem. Today I tried on 64-bit Windows 7 and get exactly the error you described, which is great for my debugging.

I investigated the header and the JPEG is a standard JPEG, but with an EXIF header. From what I read, it is not uncommon that the EXIF header maybe corrupt and some programs will just ignore it. In .NET it allows reading and (may even allow writing at some point), but not writing. You can read more about it in blog post GDI+ can’t handle some malformed JPG files.

One way to remove it is to clone the image like suggested by Marc, which will create the new image without an EXIF header, hence that explains why the file size is actually smaller. There are methods for removing an EXIF header programmatically, and some has been suggested in Stack Overflow like Simple way to remove EXIF data from a JPEG with .NET.

The suggested problem with reading the byte marker and skip the stream will not work well consistently as we are dealing with a corrupted EXIF header. I tried using RemovePropertyItem is an option, but it still doesn't work either, and my guess is because there are corrupted property items that are being referred to (when I inspect the JPEG header there are six properties, and .NET only loads four). They are other library like exiv2net which can be explored, but I suspect the outcome will be similar.

In short, the answer will be to clone the image as suggested by Marc. This is perhaps not the solution, but an insight into the problem.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Fadrian Sudaman
  • 6,405
  • 21
  • 29
0

Try checking your permissions. Not being able to save can be caused by not having the right permission to write/modify, and could generate this error.

Pbirkoff
  • 4,642
  • 2
  • 20
  • 18
  • Its not permissions. I tested with another image and it works fine. Its just this particular image that has something wrong with it. – Sam Saffron Feb 26 '10 at 08:36
-1

That error occurrs when you either have

a. no permissions to write the file
b. you are trying to write an image that is locked 
   (often, by a UI control ie. picturebox)

You'll need to dispose the original, and then save over it with your resized clone as in Marc's answer. I just figured I'd tell you why it's happening.

Steven Evers
  • 16,649
  • 19
  • 79
  • 126
  • To the downvoter: Apparently copy/paste code is vastly superior than to knowing why the problem is happening in the first place. well done – Steven Evers Mar 04 '10 at 18:03
  • I will verify that the above reasons are in fact valid. There are other causes, but I see no need for a down vote. This post would have helped me a few days ago when I was dealing with an earlier portion of the problem that caused me to search for this thread. – DCastenholz Dec 03 '12 at 21:11