How can I get .NET to save this image?
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 generi开发者_运维知识库c 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.
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);
}
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.
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.
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.
Try using WPF's BitmapSource instead of the WinForm's Image, it supports more pixel, image and file formats.
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.
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.
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.
精彩评论