2014-09-08 5 views
9

Я получаю исключение из памяти при использовании System.Drawing.Graphics.FromImage (используя последние версии .NET-программного обеспечения на сервере Windows 2012), ТОЛЬКО на очень несколько конкретных файлов изображений. В большинстве случаев код работает нормально.OutOfMemoryException: Недостаточно памяти - System.Drawing.Graphics.FromImage

Типичные ответы на вышеупомянутую проблему указывают на то, что определенные ресурсы не выпускаются.

Пожалуйста, учтите следующее, прежде чем ответить: -

  • Это специфическое изображение 34KB в размере, является .JPG изображение. Сервер простаивает и имеет более 32 ГБ оперативной памяти.
  • Если я смотрю на свойства , этот файл jpg, используя Windows Explorer, щелкнув правой кнопкой мыши по файлу, Windows говорит: 96 точек на дюйм и 32-битовая глубина.
  • НО, если я открою этот файл jpg с помощью любой графической программы (например, photoshop), свойства файла будут отображаться как: 72 dpi и 24 бит глубины.
  • Итак, есть неправильное совпадение между тем, что я думаю, что свойства заголовка файла говорят и что в действительности содержит файл.
  • Далее, если открыть файл JPG с помощью графической программы и просто повторно сохранить без изменения ничего, свойства файла в проводнике Windows теперь соответствуют/прочитана правильно (72 точек на дюйм и 24 битная глубина); и файл обрабатывается System.Drawing.Graphics правильно, без исключения исключения.

Из-за мое ограниченное знание предмета, я не знаю , если файл заголовок файла изображения может содержать различные данные от фактического содержимого файла.

Вопросы:

Как я могу исправить эту проблему? Или как я могу сообщить System.Drawing.Graphics игнорировать данные заголовка файла и просто посмотреть содержимое фактического файла изображения? (как это делают все графические программы, такие как Photoshop).

Спасибо!

+1

Как эти файлы JPEG были созданы? похоже, что их форматирование некорректно ... – Tigran

+2

С какого типа приложения вы получаете эту ошибку? Поместите некоторый код. Вы вызываете dispose на объекты, такие как system.drawing.Image, которые реализуют IDispose? – Mick

+0

Перейти к началу проблемы, которая является источником этих плохих файлов. Я видел, что jpeg плохо перепутался, например. миниатюра, показывающая что-то все неправильно, без какого-либо способа ее исправить, кроме сохранения в tif и обратно в jpeg .. – TaW

ответ

18

Хотя я не гуру в формате JPEG, я провел некоторое исследование по этому вопросу, и вот что я нашел, что могло бы помочь вам в решении вашей проблемы/вопросов.

Обратите внимание, что этот ответ предполагает, а не конкретно указывает источник вашей проблемы из-за отсутствия файла примера для проверки и указания того, что его отличает от того, что ожидает декодер .Net/GDI + JPEG/JFIF.

Формат JPEG/JFIF

Начинание, вы можете иметь некоторое представление о самом формате JPEG/JFIF. В конце концов, вы только что столкнулись с файлом, который .Net/GDI + не может загрузить/разбор. Поскольку у меня нет файла, с которым возникают проблемы, я бы предложил загрузить его в шестнадцатеричном редакторе, который имеет возможность выделить файл на основе шаблона/кода/парсера.

Я использовал 010 Editor и JPEG Template из репозитория шаблонов Sweetscape. Редактор 010 поставляется с 30-дневной бесплатной пробной версией.

Что вы конкретно ищете, SOF п идентификатор и данные в вашем плохой JPEG.

enter image description here

В SOF данных в п я могу видеть, что мое изображение Y (154) пикселей в высоту и Х (640) пикселей в ширину с точностью до 8 бит на компонент с использованием 3 компонентов, делая его 24 бит на пиксель.

Формат JPEG/JFIF - это огромное сочетание множества различных реализаций/форматов. Очевидно, что вы не найдете каждый вариант формата в любой библиотеке, которая существует с тех пор, как появились нечетные Форматы JPEG. Которая имеет библиотеку GDI +.

В вашем случае, я подозреваю, что вы запустили цветной профиль CMYK в ваших файлах JPEG.

Реализация .Net

Вы сказали, что вы использовали System.Drawing.Graphics.FromImage так что я буду считать, что ваш код выглядит как один из следующих способов:

Graphics.FromImage(Image.FromFile("nope.jpg")); 
Graphics.FromImage(Image.FromFile("nope.jpg", true)); 
Graphics.FromImage(Image.FromStream(nopeJpegStream)); 

Из этих звонков, вы может получить OutOfMemoryException, когда родной gdiplus.dll называет ...

  • GdipGetImageGraphicsContext
  • GdipLoadImageFromFile
  • GdipLoadImageFromFileICM (или их соответствующие * Стрит варианту) или
  • GdipImageForceValidation

... возвращает код 3 или 5 (из памяти или Недостаточных буферов соответственно)

Что я собрал с sourcesource.microsoft.com, просматривая источники .Net.
В любом случае это скорее всего не проблема с .Net, а проблема с GDI + (gdiplus.dll), для которой Microsoft не предоставляет исходный код. Это также означает, что нет способа контролировать загрузку изображения с использованием оберток .Net, и нет способа проверить, ПОЧЕМУ он терпит неудачу. (хотя я все еще подозреваю, что ваш JPEG сохранен с помощью CMYK)

К сожалению, вы найдете много других из этих странных исключений/ошибок, которые вы двигаетесь в GDI +. Поскольку библиотека почти устарела в пользу Windows Presentation Framework (WPF) и компонента Windows Imaging. (WIC)

Мое собственное тестирование

Поскольку вы не предоставили изображение или какие-либо дополнительные сведения по этому вопросу я попытался воспроизвести проблему. Это само по себе само по себе, Image.FromFile (GdipLoadImageFromFile) провалится во многих разных форматах файлов.По крайней мере, не важно, что такое расширение файла, что, к счастью, делает Photoshop.

Итак, я, наконец, сумел воспроизвести файл .jpg, который загружается отлично в Photoshop, показывает DPI как 96 и глубину бит как 32. Конечно, если бы я знал больше о формате JPEG, возможно, к решению сразу.

Показаны этот файл (который я должен был установить в CMYK цветового пространства в Photoshop) в 010 Редактор дал мне следующий SOF п данные: Y (154) пикселей в высоту и X (640) пикселей в ширину с точность 8 бит на компонент с использованием компонентов, составляющих 32 бита на пиксель.

Я подозреваю, что вы увидите то же самое в своем «плохом» файле.
И да, Image.FromFile теперь выбрасывает OutOfMemoryException!

Возможные решения

  • Используйте внешнюю библиотеку для загрузки файлов изображений. (Упражнение, которое я оставляю вам, но ImageMagick A.K.A Magick.NET кажется хорошей ставкой)
  • Используйте инструмент командной строки (вызывается при получении этого исключения), который может преобразовывать изображение из одного формата в другой. Или из JPEG в JPEG, как это может быть в этом случае. (Еще раз, ImageMagick в "преобразовать" инструмент командной строки, кажется, хорошая ставка)
  • Используйте Windows Presentation Framework сборки ...

    public static Image ImageFromFileWpf(string filename) { 
        /* Load the image into an encoder using the Presentation Framework. 
        * This is done by adding a frame (which in laymans terms is a layer) to a class derived BitmapEncoder. 
        * Only TIFF, Gif and JPEG XR supports multiple frames. 
        * Since we are going to convert our image to a GDI+ resource we won't support this as GDI+ doesn't (really) support it either. 
        * If you want/need support for layers/animated Gif files, create a similar method to this one that takes a BitmapFrame as an argument and then... 
        * 1. Instanciate the appropriate BitmapDecoder. 
        * 2. Iterate over the BitmapDecoders frames, feeding them to the new method. 
        * 3. Store the returned images in a collection of images. 
        * 
        * Finally, i opted to use a PngBitmapEncoder here which supports image transparency. 
        */ 
        var bitmapEncoder = new PngBitmapEncoder(); 
        bitmapEncoder.Frames.Add(BitmapFrame.Create(new Uri(filename))); 
    
        // Use a memorystream as a handover from one file format to another. 
        using (var memoryStream = new MemoryStream()) { 
         bitmapEncoder.Save(memoryStream); 
         /* We MUST create a copy of our image from stream, MSDN specifically states that the stream must remain 
         * open throughout the lifetime of the image. 
         * We cannot instanciate the Image class, so we instanciate a Bitmap from our temporary image instead. 
         * Bitmaps are derived from Image anyways, so this is perfectly fine. 
         */ 
         var tempImage = Image.FromStream(memoryStream); 
         return new Bitmap(tempImage); 
        } 
    } 
    

    на основе this answer ...

... Я бы сказал, что это хороший вариант, поскольку он держит вас в рамках .Net.
Пожалуйста, имейте в виду, что при возврате метода вы получаете изображение PNG. Если вы назовете Image.Save(string) на нем вы WILL Сохраните файл PNG, независимо от того, какое расширение вы его сохраните.

Существует перегрузка Image.Save(string, ImageFormat), которая сохранит файл, используя формат файла. Однако использование этой перегрузки с ImageFormat.Jpeg приведет к потере качества в результирующем файле на более чем одном уровне.

Это может быть несколько исправлено с помощью третьей перегрузки:

foreach (var encoder in ImageCodecInfo.GetImageEncoders()) { 
    if (encoder.MimeType == "image/jpeg") 
     image.Save(filename, encoder, new EncoderParameters { Param = new [] { new EncoderParameter(Encoder.Quality, 100L) }}); 
} 

Который, по крайней мере, сохранит JPEG с «почти» без сжатия. GDI + по-прежнему не очень хорошо справляется с этим.
Однако, независимо от того, сколько вы крутите и поворачиваете. GDI + не будет столь же хорош, как и соответствующая библиотека изображений, которая, скорее всего, будет ImageMagick. Чем дальше вы можете получить от GDI +, тем лучше будет.

Заключение/TL: DR и другие примечания.

Вопрос: Могу ли я загрузить эти файлы в .Net?
A: Да, с немного возиться и не использовать GDI + для начальной загрузки файла, так как GDI + не поддерживает цветовое пространство CMYK в файлах JPEG.
И даже при том, что GDI + не поддерживает поддержку многих вещей, поэтому я бы порекомендовал внешнюю библиотеку изображений над GDI +.

Q: Несовпадение в ДПИ и битовая глубина для файлов между Windows, и < вставкой приложением фото здесь >
A: Это просто доказательство того, что Windows, JPEG загрузка отличается от других приложений JPEG погрузочных процедур. Только приложения, которые используют GDI или GDI +, будут видеть ту же информацию, что и Windows при отображении деталей изображения.
Если вы используете Windows 7+, то он не использует GDI + для отображения информации или изображения. Для этого используется WPF или WIC, которые несколько более актуальны.

Q: Если открыть файл в формате JPG с помощью графической программы и просто повторно сохранить, ничего не меняя, свойства файла в проводнике Windows теперь соответствуют/прочитать правильно (72 точек на дюйм и 24-битовую)
A : Если вы используете Adobe Photoshop и используете «Сохранить для Интернета», изображение JPEG не будет сохранено в формате CMYK. Вместо этого используйте «Сохранить как ...», и вы обнаружите, что цветовое пространство (и глубина бит) остается неизменным.

Однако, я не смог воспроизвести ваше несоответствие в DPI и глубине бита при загрузке моего файла в Photoshop. Они сообщаются как в Windows, так и в Photoshop.

+0

Отличный ответ, но вы должны были заставить оригинальный плакат предоставить тестовое изображение. –

+0

@FrankHileman Спасибо, но я просто не мог противостоять этому. – Cadde

4

У меня была такая же проблема с этой ошибкой - кажется, что библиотека Graphics/Bitmap/Image генерирует исключение с определенными искаженными изображениями. Сузить его больше, чем это, как показывает Кэдд, трудно.

Исходя из большого ответа, сделанного Cadde (который слева, используя внешнюю библиотеку в качестве упражнения для читателя), я изменил свой код к следующему, используя MagickNet, которые вы можете получить here, или просто с NuGet: PM> Install-Package Magick.NET-Q16-x86.

Код пытается создать графический объект из изображения, а если он не работает, он использует ImageMagick для повторного загрузки изображения, преобразования в растровое изображение и попыток загрузки оттуда.

Image bitmap = Bitmap.FromFile(filename, false); 
Graphics graphics = null; 
try 
{ 
    graphics = Graphics.FromImage(bitmap); 
} 
catch (OutOfMemoryException oome) 
{ 
    // Well, this looks like a buggy image. 
    // Try using alternate method  
    ImageMagick.MagickImage image = new ImageMagick.MagickImage(filename); 
    image.Resize(image.Width, image.Height); 
    image.Quality = 90; 
    image.CompressionMethod = ImageMagick.CompressionMethod.JPEG; 
    graphics = Graphics.FromImage(image.ToBitmap());    
} 
+1

Мне кажется, что перехват OutOfMemoryException в качестве потока управления звучит как плохая практика.Вместо этого загрузите изображение с помощью ImageMagick, определите его цветовое пространство, при необходимости конвертируйте из CYMK в sRGB, а затем действуйте как обычно. Вы теряете эффективность, требуя, чтобы ImageMagick сначала проверял его, но вы не вызываете нехватку памяти в процессе ожидаемого потока. – phloopy

0

У меня была та же проблема. Мой файл jpg был создан из Photoshop. Простое решение состоит в том, чтобы открыть файл jpg с помощью Winodws Paint и сохранить как новый файл jpg. Импортируйте новый файл jpg в проект C#, и проблема исчезнет.

Смежные вопросы