У меня есть это изображение JPEG, которое открывается прекрасный в Picasa, Photoshop, веб-браузере, и т.д., но в.NET это просто отказывается работать.
Image image = Image.FromFile(@"myimage.jpg");
image.Save(@"myimage2.jpg");
// ExternalException - A generic error occurred in GDI+.
Существует ли способ восстановить его в.NET, таким образом, я могу работать с ним (я должен изменить размер его), не решая проблему в источнике?
Полные детали исключения:
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
Эта проблема восстанавливаема в Windows 7.
Кажется, он отлично работает в Windows XP и Vista, но не в Windows 7.
Мне удалось найти эту проблему в Microsoft Connect . Это не идентично вашей проблеме, но выглядит очень похожим - ExternalException
возникает при попытке повторно сохранить файл JPEG. В настоящее время на 16 репродукциях, включая некоторые комментарии о том, что проблема все еще существует в финальной версии.
Таким образом, это выглядит не ошибкой в .NET Framework, а ошибкой в Windows 7 - в частности, ошибкой в API GdipSaveImageToStream
.
Как уже упоминалось, обходной путь заключается в принудительном преобразовании в другой формат. Это то, что делают ответы Марка и Дарина. Очевидно, что в файле JPEG есть некоторая «лишняя» информация, которая вызывает ошибку в Win7. Когда вы конвертируете в другой формат или делаете копию растрового изображения, эта информация (может быть, EXIF?) Удаляется.
Попробуйте явно указать формат:
using (Image image = Image.FromFile(@"test.jpg"))
{
image.Save(@"myimage2.gif", ImageFormat.Gif);
}
Все ImageFormat.Png
, ImageFormat.Bmp
и ] ImageFormat.Gif
работает нормально. ImageFormat.Jpeg
вызывает исключение.
Исходное изображение имеет формат JFIF
, поскольку оно начинается с байтов FF D8 FF E0
.
Попробуйте использовать WPF BitmapSource вместо изображения WinForm, он поддерживает больше форматов пикселей, изображений и файлов.
Попробуйте проверить свои разрешения. Невозможность сохранения может быть вызвана отсутствием прав на запись / изменение и может вызвать эту ошибку.
Эта ошибка возникает, когда у вас либо есть
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)
. Вам нужно будет удалить оригинал, а затем сохранить его вместе с клоном с измененным размером, как в ответе Марка. . Я просто подумал, что скажу вам, почему это происходит.
Кажется, это работает:
using (Image image = Image.FromFile(@"c:\dump\myimage.jpg"))
using (Image clone = new Bitmap(image))
{
clone.Save(@"c:\dump\myimage2.jpg", ImageFormat.Jpeg);
}
image
на самом деле является Bitmap
в любом случае, так что это должно быть похоже. Странно, но myimage2
на 5k меньше - радости jpeg ;-p
Приятным моментом является то, что вы можете изменять размер одновременно (ваше фактическое намерение):
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);
}
Вчера я пробовал использовать 32-битную Windows XP и не могу воспроизвести проблему. Сегодня я попробовал на 64-битной Windows 7 и получил именно ту ошибку, которую вы описали, что отлично подходит для моей отладки.
Я исследовал заголовок, и JPEG является стандартным форматом JPEG, но с заголовком EXIF. Из того, что я читал, нередко бывает, что заголовок EXIF может быть поврежден, и некоторые программы просто игнорируют его. В .NET он позволяет читать и (в какой-то момент может даже разрешать запись), но не записывает. Подробнее об этом можно прочитать в сообщении блога GDI + не может обрабатывать некоторые искаженные файлы JPG .
Один из способов удалить его - клонировать изображение, как это было предложено Марком, что создаст новое изображение без заголовка EXIF, что объясняет, почему размер файла на самом деле меньше. Существуют методы программного удаления заголовка EXIF, и некоторые из них были предложены в Stack Overflow , например Простой способ удаления данных EXIF из JPEG с помощью .NET .
Предлагаемая проблема с чтением байтового маркера и пропуском потока не будет работать должным образом, поскольку мы имеем дело с поврежденным заголовком EXIF. Я попытался использовать RemovePropertyItem - это вариант, но он все равно не работает, и я предполагаю, что есть поврежденные элементы свойств, на которые ссылаются (когда я проверяю заголовок JPEG, есть шесть свойств, а .NET загружает только четыре ). Это другая библиотека, такая как exiv2net, которую можно изучить, но я подозреваю, что результат будет аналогичным.
Короче говоря, ответ будет заключаться в клонировании изображения, как предлагает Марк. Возможно, это не решение, а понимание проблемы.
Это давняя ошибка в самом фреймворке .NET, которую я не ожидаю увидеть исправленной в ближайшее время. Есть несколько связанных ошибок, с которыми я также столкнулся, которые вызывают "общую ошибку в GDI+", в том числе если вы обращаетесь к коллекции PropertyItem для некоторых JPEG (чтобы проверить коды EXIF), то с этого момента вы не сможете сохранить изображение. Кроме того, без всякой причины некоторые JPEG, как, например, тот, что у вас здесь, просто не сохраняются. Обратите внимание, что простое сохранение в другом формате тоже не всегда срабатывает, хотя в данном случае сработало.
Обходной путь, который я использовал в своем приложении, потребляющем изображения со всего Интернета (и поэтому сталкивающемся с более чем справедливой долей подобных проблем), - это перехватить ExternalException и скопировать изображение в новый Bitmap, как в одном из предыдущих ответов, однако простое сохранение в новом JPEG сильно снижает качество, поэтому я использую код, подобный приведенному ниже, чтобы сохранить высокое качество:
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;
}
}
}
Обратите внимание, что вы потеряете информацию EXIF, но я пока оставлю это (как правило, вы все равно сможете прочитать коды EXIF, даже если сохранение исходного изображения не удастся). Я уже давно оставил попытки выяснить, что именно не нравится .NET в конкретных изображениях (и у меня есть примеры различных неудачных случаев, если кто-то захочет взяться за эту задачу), но приведенный выше подход работает, что приятно.