Как я могу заставить.NET сохранять этот образ?

У меня есть это изображение 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.

9
задан Peter Mortensen 17 November 2018 в 09:36
поделиться

8 ответов

Кажется, он отлично работает в Windows XP и Vista, но не в Windows 7.

Мне удалось найти эту проблему в Microsoft Connect . Это не идентично вашей проблеме, но выглядит очень похожим - ExternalException возникает при попытке повторно сохранить файл JPEG. В настоящее время на 16 репродукциях, включая некоторые комментарии о том, что проблема все еще существует в финальной версии.

Таким образом, это выглядит не ошибкой в ​​.NET Framework, а ошибкой в ​​Windows 7 - в частности, ошибкой в ​​API GdipSaveImageToStream .

Как уже упоминалось, обходной путь заключается в принудительном преобразовании в другой формат. Это то, что делают ответы Марка и Дарина. Очевидно, что в файле JPEG есть некоторая «лишняя» информация, которая вызывает ошибку в Win7. Когда вы конвертируете в другой формат или делаете копию растрового изображения, эта информация (может быть, EXIF?) Удаляется.

3
ответ дан 4 December 2019 в 08:15
поделиться

Попробуйте явно указать формат:

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 .

4
ответ дан 4 December 2019 в 08:15
поделиться

Попробуйте использовать WPF BitmapSource вместо изображения WinForm, он поддерживает больше форматов пикселей, изображений и файлов.

2
ответ дан 4 December 2019 в 08:15
поделиться

Попробуйте проверить свои разрешения. Невозможность сохранения может быть вызвана отсутствием прав на запись / изменение и может вызвать эту ошибку.

0
ответ дан 4 December 2019 в 08:15
поделиться

Эта ошибка возникает, когда у вас либо есть

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)

. Вам нужно будет удалить оригинал, а затем сохранить его вместе с клоном с измененным размером, как в ответе Марка. . Я просто подумал, что скажу вам, почему это происходит.

-1
ответ дан 4 December 2019 в 08:15
поделиться

Кажется, это работает:

    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);
    }
12
ответ дан 4 December 2019 в 08:15
поделиться

Вчера я пробовал использовать 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, которую можно изучить, но я подозреваю, что результат будет аналогичным.

Короче говоря, ответ будет заключаться в клонировании изображения, как предлагает Марк. Возможно, это не решение, а понимание проблемы.

2
ответ дан 4 December 2019 в 08:15
поделиться

Это давняя ошибка в самом фреймворке .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 в конкретных изображениях (и у меня есть примеры различных неудачных случаев, если кто-то захочет взяться за эту задачу), но приведенный выше подход работает, что приятно.

4
ответ дан 4 December 2019 в 08:15
поделиться