2008-10-16 82 views
44

Estoy cargando una imagen de un archivo, y quiero saber cómo validar la imagen antes de que se lea completamente del archivo.Validar imagen del archivo en C#

string filePath = "image.jpg"; 
Image newImage = Image.FromFile(filePath); 

El problema se produce cuando image.jpg no es realmente un jpg. Por ejemplo, si creo un archivo de texto vacío y lo cambio a image.jpg, se lanzará una excepción OutOfMemory cuando se cargue image.jpg.

Estoy buscando una función que valide una imagen dada una secuencia o una ruta de archivo de la imagen.

Ejemplo función prototipo

bool IsValidImage(string fileName); 
bool IsValidImage(Stream imageStream); 
+3

Por qué no envuelva a ese código en un bloque try ... catch, y si se lanza esta excepción, puede lo considera "inválido"? Por supuesto, esta es una heurística ingenua, pero cumple su función. Cualquier otra cosa igual tendrá que abrir el archivo, por lo que no va a guardar una cantidad significativa en cuanto a rendimiento, independientemente de IMO. –

+0

Vea también: http://stackoverflow.com/questions/9354747/how-can-i-determine-if-a-file-is-an-image-file-in-net – Daryl

Respuesta

21

Los archivos JPEG no tienen una definición formal de encabezado, pero tienen una pequeña cantidad de metadatos que puede usar.

  • desplazamiento 0 (dos bytes): JPEG SOI marcador (FFD8 hex)
  • Offset 2 (dos bytes): Ancho de la imagen en píxeles
  • Offset 4 (dos bytes): Altura de la imagen en píxeles
  • Offset 6 (Byte): Número de componentes (1 = escala de grises, RGB = 3)

Hay un par de otras cosas después de eso, pero los que no son importantes.

Puede abrir el archivo utilizando una secuencia binaria, y leer estos datos iniciales, y asegúrese de que la posición 0 es 0, y el desplazamiento 6 es ya sea 1,2 o 3.

que por lo menos le dará un poco más de precisión.

o simplemente puede atrapar la excepción y seguir adelante, pero pensé que quería un reto :)

+0

Habría seguido leyendo el encabezado del archivo y lo comparé con en una matriz de encabezados de archivo de imágenes admitidas por .NET. Eventualmente, voy a codificarlo y publicarlo como una solución para cualquier persona que lo necesite en el futuro. – SemiColon

+1

El solo hecho de leer los encabezados no garantiza que el archivo sea válido y no generará una excepción cuando se abra en Image.FromFile(). – MusiGenesis

+2

No, pero no dije que lo haría. – FlySwat

1

me gustaría crear un método como:

Image openImage(string filename); 

en el que controlar la excepción. Si el valor devuelto es nulo, hay un nombre/tipo de archivo no válido.

+0

LOL, debo haber estado escribiendo eso como un comentario cuando publicaste esto. Estoy de acuerdo con esta respuesta, es lo suficientemente simple para hacer el trabajo. –

+0

Este método está un poco equivocado. No debe controlar el flujo del programa utilizando excepciones. Además ... Las excepciones devueltas de esa llamada en particular pueden ser * muy * engañosas y ambiguas. –

+0

No veo qué pasa con esto. La persona que escribió openImage eligió lanzar una excepción si la imagen no es válida en lugar de proporcionar un valor de retorno. Por lo tanto, me parece que atrapar y manejar la excepción es la forma en que tenían la intención de que usted manejara esa situación. – pilavdzice

0

Se podía leer los primeros bytes de la corriente y compararlos con los bytes de cabecera mágicas para JPEG.

30

Uso de Windows Forms:

bool IsValidImage(string filename) 
{ 
    try 
    { 
     using(Image newImage = Image.FromFile(filename)) 
     {} 
    } 
    catch (OutOfMemoryException ex) 
    { 
     //The file does not have a valid image format. 
     //-or- GDI+ does not support the pixel format of the file 

     return false; 
    } 
    return true; 
} 

De lo contrario, si usted está usando WPF se puede hacer lo siguiente:

bool IsValidImage(string filename) 
{ 
    try 
    { 
     using(BitmapImage newImage = new BitmapImage(filename)) 
     {} 
    } 
    catch(NotSupportedException) 
    { 
     // System.NotSupportedException: 
     // No imaging component suitable to complete this operation was found. 
     return false; 
    } 
    return true; 
} 

Debe liberar la imagen creada. De lo contrario, cuando llame a esta función un gran número de veces, esto arrojaría OutOfMemoryException porque el sistema se quedó sin recursos, y no porque la imagen esté corrupta produciendo un resultado incorrecto, y si elimina imágenes después de este paso, usted potencialmente borrando los buenos

+0

Gracias :). Estaba pensando en hacer eso, pero me preguntaba si había una forma de hacerlo que ya esté incorporada en .NET Framework. Como nadie más mencionó ninguna función incorporada en .NET Framework para hacer esto, creo que esta sería una buena solución. – SemiColon

+1

Probablemente debería detectar OutOfMemoryException, que es la excepción documentada lanzada si el formato del archivo no es válido. Esto significa que permitiría que FileNotFoundException se propague a la persona que llama. – Joe

+0

No me di cuenta de que era la excepción documentada para un archivo de imagen no válido. Simplemente asumí que podría haber diferentes excepciones lanzadas en base a qué era exactamente lo que estaba mal con el archivo. Gracias. – MusiGenesis

11

Puede hacer un tipado aproximado olfateando el encabezado.

Esto significa que cada formato de archivo se implementa tendrá que tener una cabecera de identificación ...

JPEG: primeros 4 bytes son FF FF D8 E0 (en realidad sólo los primeros dos bytes harían por jpeg no jfif , más información here).

GIF: Primero 6 bytes son o bien "GIF87a" o "89a" (más información here)

PNG: primeros 8 bytes son: 89 50 4E 47 0D 0A 1A 0A (más información here)

TIFF: los primeros 4 bytes son: II42 o MM42 (más información here)

etc ... puede encontrar información de encabezado/formato para casi cualquier formato de gráficos que le interese y agregar lo que maneja según sea necesario. Lo que no hará, es decirle si el archivo es una versión válida de ese tipo, pero le dará una pista sobre "imagen, no imagen". Todavía podría ser una imagen corrupta o incompleta y, por lo tanto, fallar cuando se abra, por lo que todavía es necesario intentar capturar la llamada .FromFile.

+5

hmm .. cuatro personas respondieron mientras escribía eso y recogiendo enlaces. Lugar ocupado. –

18

Bueno, me adelanté y codifiqué una serie de funciones para resolver el problema. Primero verifica el encabezado y luego intenta cargar la imagen en un bloque try/catch. Solo comprueba si hay archivos GIF, BMP, JPG y PNG. Puede agregar fácilmente más tipos agregando un encabezado a imageHeaders.

static bool IsValidImage(string filePath) 
{ 
    return File.Exists(filePath) && IsValidImage(new FileStream(filePath, FileMode.Open, FileAccess.Read)); 
} 

static bool IsValidImage(Stream imageStream) 
{ 
    if(imageStream.Length > 0) 
    { 
     byte[] header = new byte[4]; // Change size if needed. 
     string[] imageHeaders = new[]{ 
       "\xFF\xD8", // JPEG 
       "BM",  // BMP 
       "GIF",  // GIF 
       Encoding.ASCII.GetString(new byte[]{137, 80, 78, 71})}; // PNG 

     imageStream.Read(header, 0, header.Length); 

     bool isImageHeader = imageHeaders.Count(str => Encoding.ASCII.GetString(header).StartsWith(str)) > 0; 
     if (isImageHeader == true) 
     { 
      try 
      { 
       Image.FromStream(imageStream).Dispose(); 
       imageStream.Close(); 
       return true; 
      } 

      catch 
      { 

      } 
     } 
    } 

    imageStream.Close(); 
    return false; 
} 
+0

No del todo. Si imageStream.Read arroja una excepción, aún no la cierra. Lo mejor es poner una declaración de uso alrededor de la creación de instancias de la ruta. – Joe

+6

@Joe No estoy de acuerdo. No debería cerrar o eliminar la transmisión en esta función. Esta función no creó la secuencia, por lo que no debería realizar comportamientos inesperados. Además ... En caso de éxito, Image.FromStream consumirá la transmisión (que podría ser de solo lectura y no se puede restablecer), lo que significa que una lectura posterior de la transmisión fallaría más adelante ya que la transmisión ya se había consumido. Además, en caso de éxito, la imagen se carga (es muy costosa) y luego se desecha de inmediato. Si este método devuelve verdadero, es probable que la persona que llama cargue la imagen en la siguiente línea. Entonces eso es un doble trabajo. –

+0

@Troy, estoy de acuerdo. Sería mejor para este método tomar una matriz de bytes o algún objeto similar que no se vea afectado por el método, especialmente porque es estático. –

0

en caso yo necesitan que los datos leídos para otras operaciones y/o para otros tipos de archivo (PSD por ejemplo), más adelante, a continuación, utilizando la función de Image.FromStream no es necesariamente un buen IDEEA.

3

Un método que soporta TIFF y JPEG también

private bool IsValidImage(string filename) 
{ 
    Stream imageStream = null; 
    try 
    { 
     imageStream = new FileStream(filename, FileMode.Open); 

     if (imageStream.Length > 0) 
     { 
      byte[] header = new byte[30]; // Change size if needed. 
      string[] imageHeaders = new[] 
      { 
       "BM",  // BMP 
       "GIF",  // GIF 
       Encoding.ASCII.GetString(new byte[]{137, 80, 78, 71}),// PNG 
       "MM\x00\x2a", // TIFF 
       "II\x2a\x00" // TIFF 
      }; 

      imageStream.Read(header, 0, header.Length); 

      bool isImageHeader = imageHeaders.Count(str => Encoding.ASCII.GetString(header).StartsWith(str)) > 0; 
      if (imageStream != null) 
      { 
       imageStream.Close(); 
       imageStream.Dispose(); 
       imageStream = null; 
      } 

      if (isImageHeader == false) 
      { 
       //Verify if is jpeg 
       using (BinaryReader br = new BinaryReader(File.Open(filename, FileMode.Open))) 
       { 
        UInt16 soi = br.ReadUInt16(); // Start of Image (SOI) marker (FFD8) 
        UInt16 jfif = br.ReadUInt16(); // JFIF marker 

        return soi == 0xd8ff && (jfif == 0xe0ff || jfif == 57855); 
       } 
      } 

      return isImageHeader; 
     } 

     return false; 
    } 
    catch { return false; } 
    finally 
    { 
     if (imageStream != null) 
     { 
      imageStream.Close(); 
      imageStream.Dispose(); 
     } 
    } 
} 
6

Esto debería hacer el truco - usted no tiene que leer bytes sin formato de la cabecera:

using(Image test = Image.FromFile(filePath)) 
{ 
    bool isJpeg = (test.RawFormat.Equals(ImageFormat.Jpeg)); 
} 

Por supuesto, también debe atrapar la excepción OutOfMemoryException, que lo salvará si el archivo no es una imagen en absoluto.

Y, ImageFormat tiene elementos preestablecidos para todos los otros tipos de imágenes importantes que admite GDI +.

Nota, debe utilizar .Equals() y no == en objetos ImageFormat (no es una enumeración) porque el operador == no está sobrecargado para llamar al método Equals.

1

Tomé la respuesta de coma y se convierte en VB:

Private Function IsValidImage(imageStream As System.IO.Stream) As Boolean 

      If (imageStream.Length = 0) Then 
       isvalidimage = False 
       Exit Function 
      End If 

      Dim pngByte() As Byte = New Byte() {137, 80, 78, 71} 
      Dim pngHeader As String = System.Text.Encoding.ASCII.GetString(pngByte) 

      Dim jpgByte() As Byte = New Byte() {255, 216} 
      Dim jpgHeader As String = System.Text.Encoding.ASCII.GetString(jpgByte) 

      Dim bmpHeader As String = "BM" 
      Dim gifHeader As String = "GIF" 

      Dim header(3) As Byte 

      Dim imageHeaders As String() = New String() {jpgHeader, bmpHeader, gifHeader, pngHeader} 
      imageStream.Read(header, 0, header.Length) 

      Dim isImageHeader As Boolean = imageHeaders.Count(Function(str) System.Text.Encoding.ASCII.GetString(header).StartsWith(str)) > 0 

      If (isImageHeader) Then 
       Try 
        System.Drawing.Image.FromStream(imageStream).Dispose() 
        imageStream.Close() 
        IsValidImage = True 
        Exit Function 
       Catch ex As Exception 
        System.Diagnostics.Debug.WriteLine("Not an image") 
       End Try 
      Else 
       System.Diagnostics.Debug.WriteLine("Not an image") 
      End If 

      imageStream.Close() 
      IsValidImage = False 
     End Function 
51

aquí es mi cheque imagen. No puedo confiar en las extensiones de archivos y tengo que verificar el formato por mi cuenta. Estoy cargando BitmapImages en WPF desde matrices de bytes y no sé el formato por adelantado. WPF detecta el formato fino pero no le dice el formato de imagen de los objetos BitmapImage (al menos no conozco una propiedad para esto). Y no quiero cargar la imagen nuevamente con System.Drawing solo para detectar el formato. Esta solución es rápida y funciona bien para mí.

public enum ImageFormat 
{ 
    bmp, 
    jpeg, 
    gif, 
    tiff, 
    png, 
    unknown 
} 

public static ImageFormat GetImageFormat(byte[] bytes) 
{ 
    // see http://www.mikekunz.com/image_file_header.html 
    var bmp = Encoding.ASCII.GetBytes("BM");  // BMP 
    var gif = Encoding.ASCII.GetBytes("GIF"); // GIF 
    var png = new byte[] { 137, 80, 78, 71 }; // PNG 
    var tiff = new byte[] { 73, 73, 42 };   // TIFF 
    var tiff2 = new byte[] { 77, 77, 42 };   // TIFF 
    var jpeg = new byte[] { 255, 216, 255, 224 }; // jpeg 
    var jpeg2 = new byte[] { 255, 216, 255, 225 }; // jpeg canon 

    if (bmp.SequenceEqual(bytes.Take(bmp.Length))) 
     return ImageFormat.bmp; 

    if (gif.SequenceEqual(bytes.Take(gif.Length))) 
     return ImageFormat.gif; 

    if (png.SequenceEqual(bytes.Take(png.Length))) 
     return ImageFormat.png; 

    if (tiff.SequenceEqual(bytes.Take(tiff.Length))) 
     return ImageFormat.tiff; 

    if (tiff2.SequenceEqual(bytes.Take(tiff2.Length))) 
     return ImageFormat.tiff; 

    if (jpeg.SequenceEqual(bytes.Take(jpeg.Length))) 
     return ImageFormat.jpeg; 

    if (jpeg2.SequenceEqual(bytes.Take(jpeg2.Length))) 
     return ImageFormat.jpeg; 

    return ImageFormat.unknown; 
} 
+0

El código anterior estaba fallando para un archivo PNG en particular. Cuando lo verifiqué, los primeros 4 bytes contenían '{80, 75, 3, 4}' en lugar de la secuencia que mencionaste. La imagen puede ser abierta por los visualizadores/editores normales. ¿Que esta pasando? – dotNET

+0

Encabezados adicionales: http://www.garykessler.net/library/file_sigs.html – juFo

+0

Tengo un JPEG con 255,216,255,237 por lo que esto no funciona. –

0

Noté un par de problemas con todas las funciones anteriores. Primero de todos - Imagen.FromFile abre la imagen dada y luego causará un error de archivo abierto. Quien quiera abrir un archivo de imagen dado por cualquier razón. Incluso la aplicación en sí misma, así que cambié usando Image.FromStream.

Después de cambiar los cambios de tipo de excepción de OutOfMemoryException a ArgumentException por algún motivo poco claro para mí. (Probablemente .net framework bug?)

También si .net agregará más formatos de archivo de imagen que actualmente lo comprobaremos por función - tiene sentido primero intentar cargar la imagen si solo falla luego de eso para informar error.

Así que mi código es ahora así:

try { 
    using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read)) 
    { 
     Image im = Image.FromStream(stream); 
     // Do something with image if needed. 
    } 
} 
catch (ArgumentException) 
{ 
    if(!IsValidImageFormat(path)) 
     return SetLastError("File '" + fileName + "' is not a valid image"); 

    throw; 
} 

Dónde:

/// <summary> 
/// Check if we have valid Image file format. 
/// </summary> 
/// <param name="path"></param> 
/// <returns>true if it's image file</returns> 
public static bool IsValidImageFormat(String path) 
{ 
    using (FileStream fs = File.OpenRead(path)) 
    { 
     byte[] header = new byte[10]; 
     fs.Read(header, 0, 10); 

     foreach (var pattern in new byte[][] { 
        Encoding.ASCII.GetBytes("BM"), 
        Encoding.ASCII.GetBytes("GIF"), 
        new byte[] { 137, 80, 78, 71 },  // PNG 
        new byte[] { 73, 73, 42 },   // TIFF 
        new byte[] { 77, 77, 42 },   // TIFF 
        new byte[] { 255, 216, 255, 224 }, // jpeg 
        new byte[] { 255, 216, 255, 225 } // jpeg canon 
      }) 
     { 
      if (pattern.SequenceEqual(header.Take(pattern.Length))) 
       return true; 
     } 
    } 

    return false; 
} //IsValidImageFormat 
Cuestiones relacionadas