c# - OutOfMemoryException: memoria insuficiente-System.Drawing.Graphics.FromImage
.net (3)
Obtengo una excepción de falta de memoria cuando uso System.Drawing.Graphics.FromImage (usando las últimas versiones del software .NET en el servidor de Windows 2012), SOLAMENTE en unos pocos archivos de imagen específicos . La mayoría de las veces el código funciona bien.
Las respuestas típicas al problema anterior indican que ciertos recursos no se están liberando.
Por favor, considere lo siguiente antes de responder:
- Esta imagen específica tiene 34 KB de tamaño, es una imagen .JPG. El servidor está inactivo y tiene más de 32 GB de RAM.
- Si miro las propiedades de este archivo jpg, usando Windows Explorer, haciendo clic derecho en el archivo, Windows dice: 96 ppp y 32 bit de profundidad .
- PERO, si abro este archivo jpg usando cualquier programa de gráficos (por ejemplo, photoshop), las propiedades del archivo se muestran como: 72 ppp y 24 bits de profundidad .
- Por lo tanto, existe un desajuste entre lo que creo que dicen las propiedades del encabezado del archivo y lo que realmente contiene el archivo .
- Además, si abro el archivo jpg usando un programa de gráficos y simplemente vuelvo a guardar sin cambiar nada , las propiedades del archivo en el explorador de Windows ahora coinciden / leen correctas (72 ppp y 24 bits de profundidad); y el archivo es procesado por System.Drawing.Graphics correctamente, sin tirar la excepción .
Debido a mi conocimiento limitado del tema, no sé si el encabezado del archivo de imagen puede contener datos diferentes del contenido real del archivo .
Preguntas:
¿Como puedo solucionar este problema? ¿O cómo puedo decirle a System.Drawing.Graphics que ignore los datos del encabezado del archivo y solo observe el contenido real del archivo de imagen? (como todos los programas de gráficos como Photoshop parecen hacer).
¡Gracias!
Tuve el mismo problema con este error: parece que la biblioteca Graphics / Bitmap / Image arroja una excepción con ciertas imágenes mal formadas. Reducirlo más que eso, como lo muestra Cadde, es difícil.
Siguiendo con la gran respuesta hecha por Cadde (que salió usando una biblioteca externa como ejercicio para el lector), cambié mi código al siguiente usando MagickNet que puedes obtener aquí , o simplemente con NuGet: PM> Install-Package Magick.NET-Q16-x86
.
El código intenta crear un objeto Graphics a partir de la imagen, y si falla, utiliza ImageMagick para cargar la imagen nuevamente, convertirla en un mapa de bits e intenta cargar desde allí.
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());
}
Si bien no soy un gurú en el formato de archivo JPEG, hice algunas investigaciones sobre el tema y esto es lo que encontré que podría ayudarlo con su problema / preguntas.
Tenga en cuenta que esta respuesta asumirá en lugar de señalar específicamente el origen de su problema debido a la falta de un archivo de ejemplo para inspeccionar y decir qué difiere de lo que el decodificador .Net / GDI + JPEG / JFIF espera.
El formato JPEG / JFIF
Comenzando, es posible que desee tener alguna idea sobre el formato JPEG / JFIF. Después de todo, acaba de encontrar un archivo que .Net / GDI + no puede cargar / analizar. Como no tengo el archivo con el que experimenta problemas, le sugiero que lo cargue en un editor hexadecimal de su elección ... que tenga la capacidad de resaltar el archivo basado en una plantilla / código / analizador.
Utilicé 010 Editor y la plantilla JPEG del repositorio de plantillas en línea de Sweetscape. 010 Editor viene con una prueba gratuita de 30 días.
Lo que está buscando específicamente es el identificador SOF ny los datos en su JPEG malo .
En los datos SOF n puedo ver que mi imagen tiene Y (154) píxeles de alto y X (640) píxeles de ancho con una precisión de 8 bits por componente usando 3 componentes, por lo que es de 24 bits por píxel.
El formato JPEG / JFIF es una gran mezcla de muchas implementaciones / formatos diferentes. Obviamente, no encontrará todas las variantes del formato en ninguna biblioteca que haya existido desde hace mucho tiempo antes de que aparecieran los formatos JPEG. Que tiene la biblioteca GDI +.
En su caso, sospecho que se ha topado con el perfil de color CMYK comúnmente solicitado en sus archivos JPEG.
La implementación de .Net
Dijiste que usaste System.Drawing.Graphics.FromImage así que asumiré que tu código se parece a uno de los siguientes:
Graphics.FromImage(Image.FromFile("nope.jpg"));
Graphics.FromImage(Image.FromFile("nope.jpg", true));
Graphics.FromImage(Image.FromStream(nopeJpegStream));
A partir de esas llamadas, puede obtener una OutOfMemoryException cuando las llamadas nativas gdiplus.dll ...
- GdipGetImageGraphicsContext
- GdipLoadImageFromFile
- GdipLoadImageFromFileICM (o sus respectivas * variantes de Stream) o
- GdipImageForceValidation
... devuelve el código 3 o 5 (memoria insuficiente o memoria insuficiente, respectivamente)
Que reuní de referencesource.microsoft.com mirando a través de las fuentes de .Net allí.
En cualquier caso, esto probablemente no es un problema con .Net, pero es un problema con GDI + (gdiplus.dll) que Microsoft no proporciona el código fuente. Lo que también significa que no hay forma de controlar cómo se carga la imagen utilizando los contenedores .Net y no hay forma de verificar por qué falla. (aunque sigo sospechando que su JPEG se guardó con CMYK)
Desafortunadamente, vas a encontrar muchas más de estas extrañas excepciones / errores a medida que avanzas en GDI + land. Como la biblioteca está prácticamente en desuso, a favor de Windows Presentation Framework (WPF) y Windows Imaging Component. (WIC)
Mi propia prueba
Como nunca proporcionó una imagen o detalles adicionales sobre el tema, intenté reproducir su problema. Que era una tarea en sí misma, Image.FromFile (GdipLoadImageFromFile) fallará en muchos formatos de archivo diferentes. Al menos no le importa la extensión del archivo, que afortunadamente Photoshop sí lo hace.
Entonces, con su información, finalmente logré reproducir un archivo .jpg que funciona bien en Photoshop, muestra DPI como 96 y profundidad de bits como 32. Por supuesto, si supiera más sobre el formato JPEG, probablemente podría haber llegado a la solución correcta. lejos.
Mostrando este archivo (que tuve que configurar en el espacio de color CMYK en Photoshop) en 010 Editor me dio los siguientes datos SOF n : Y (154) píxeles de alto y X (640) píxeles de ancho con una precisión de 8 bits por componente usando 4 componentes, por lo que es de 32 bits por píxel.
Sospecho que verías lo mismo en tu archivo "malo".
Y sí, Image.FromFile ahora arroja una OutOfMemoryException!
Soluciones posibles
- Use una biblioteca externa para cargar archivos de imagen. (Un ejercicio te dejo pero ImageMagick AKA Magick.NET parece una buena apuesta)
- Haga uso de una herramienta de línea de comandos (invocada cuando obtiene esta excepción) que puede convertir una imagen de un formato a otro. O de JPEG a JPEG como puede ser en este caso. (Una vez más, la herramienta de línea de comando "convertir" de ImageMagick parece una buena apuesta)
Use los ensamblados de 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); } }
Basado en esta respuesta ...
... Lo que diría es una buena opción, ya que lo mantiene dentro del marco .Net.
Tenga en cuenta que cuando el método retorna, recupera específicamente una imagen PNG. Si llama a Image.Save(string)
, guardará un archivo PNG, sin importar en qué extensión lo guarde.
Hay una sobrecarga Image.Save(string, ImageFormat)
que guardará el archivo usando el formato de archivo deseado. Sin embargo, usar esa sobrecarga con ImageFormat.Jpeg
causará una pérdida de calidad en el archivo resultante en más de un nivel.
Eso se puede remediar de alguna manera usando la tercera sobrecarga:
foreach (var encoder in ImageCodecInfo.GetImageEncoders()) {
if (encoder.MimeType == "image/jpeg")
image.Save(filename, encoder, new EncoderParameters { Param = new [] { new EncoderParameter(Encoder.Quality, 100L) }});
}
Que, al menos, guardará un archivo JPEG con "casi" sin compresión. GDI + todavía no hace un buen trabajo en eso.
Sin embargo, no importa cuánto lo tuerza y gire. GDI + no será tan bueno como una biblioteca de imágenes adecuada, que una vez más sería ImageMagick. Cuanto más lejos se pueda obtener de GDI +, mejor estará.
Conclusión / TL: DR y otras notas.
P: ¿Puedo cargar estos archivos en .Net?
R: Sí, con un poco de manipulación y sin usar GDI + para la carga inicial del archivo, ya que GDI + no admite el espacio de color CMYK en archivos JPEG.
Y aún así, GDI + carece de soporte para muchas cosas, por lo que recomendaría una biblioteca de imágenes externa sobre GDI +.
Q: falta de coincidencia en PPP y profundidad de bits para el archivo entre Windows y <insertar la aplicación de fotos aquí>
R: Esto es solo una prueba de que la carga de Windows JPEG difiere de otras aplicaciones en las rutinas de carga JPEG. Solo las aplicaciones que usan GDI o GDI + verán la misma información que Windows al mostrar los detalles de la imagen.
Si está usando Windows 7+, entonces no está usando GDI + para mostrar la información ni la imagen. Está usando WPF o WIC para hacerlo, algo más actualizado.
P: Si abro el archivo jpg usando un programa de gráficos y simplemente vuelvo a guardar sin cambiar nada, las propiedades del archivo en el explorador de Windows ahora coinciden / leen correctas (72 ppp y profundidad de 24 bits)
R: Si usa Adobe Photoshop y usa "Guardar para web", la imagen JPEG no se guardará en formato CMYK. Use "Guardar como ..." en su lugar y encontrará que el espacio de color (y la profundidad de bits) se mantiene igual.
Sin embargo, no pude reproducir su discrepancia en DPI y profundidad de bits al cargar mi archivo en Photoshop. Se informa que son iguales en Windows y Photoshop.
Yo tuve el mismo problema. Mi archivo jpg fue generado desde Photoshop. Una solución simple es abrir el archivo jpg con Winodws Paint y guardarlo como un nuevo archivo jpg. Importe el nuevo archivo jpg al proyecto C # y el problema desaparecerá.