una - validar nombre de archivo c#
Validar imagen desde archivo en C# (12)
Estoy cargando una imagen de un archivo, y quiero saber cómo validar la imagen antes de que se lea completamente desde el archivo.
string filePath = "image.jpg";
Image newImage = Image.FromFile(filePath);
El problema ocurre 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 de función prototipo
bool IsValidImage(string fileName);
bool IsValidImage(Stream imageStream);
Bueno, seguí 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;
}
Crearía un método como:
Image openImage(string filename);
en el que manejo la excepción Si el valor devuelto es nulo, hay un nombre / tipo de archivo no válido.
Esto debería ser el truco: no tiene que leer los bytes sin procesar fuera del encabezado:
using(Image test = Image.FromFile(filePath))
{
bool isJpeg = (test.RawFormat.Equals(ImageFormat.Jpeg));
}
Por supuesto, también debe atrapar 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 principales que admite GDI +.
Tenga en cuenta que debe usar .Equals () y no == en objetos ImageFormat (no es una enumeración) porque el operador == no está sobrecargado para llamar al método Equals.
Los archivos JPEG no tienen una definición formal de encabezado, pero tienen una pequeña cantidad de metadatos que puede usar.
- Offset 0 (dos bytes): marcador JPEG SOI (hex FFD8)
- Offset 2 (Two Bytes): ancho de la imagen en píxeles
- Compensación 4 (dos bytes): altura de la imagen en píxeles
- Offset 6 (Byte): Número de componentes (1 = escala de grises, 3 = RGB)
Hay un par de otras cosas después de eso, pero esas no son importantes.
Puede abrir el archivo usando una secuencia binaria y leer estos datos iniciales, y asegurarse de que OffSet 0 sea 0 y OffSet 6 sea 1,2 o 3.
Eso al menos te daría un poco más de precisión.
O puedes atrapar la excepción y seguir, pero pensé que querías un desafío :)
Noté un par de problemas con todas las funciones anteriores. En primer lugar, Image.FromFile abre la imagen dada y luego causará un error de archivo abierto a quien quiera abrir el 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 api - exception de OutOfMemoryException a ArgumentException, por algún motivo poco claro para mí. (Probablemente el error de .net framework?)
Además, si .net agregará más formatos de archivo de imagen que actualmente, comprobaremos por función (tiene sentido primero intentar cargar la imagen si solo falla), solo después de eso para informar el error.
Entonces mi código se ve 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
Puede hacer una tipificación aproximada olfateando el encabezado.
Esto significa que cada formato de archivo que implemente necesitará tener un encabezado identificable ...
JPEG: los primeros 4 bytes son FF D8 FF E0 (de hecho, solo los primeros dos bytes lo harían para jpeg no jfif, más información here ).
GIF: los primeros 6 bytes son "GIF87a" o "GIF89a" (más información here )
PNG: los 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 a las cosas 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.
Puede leer los primeros bytes del Stream y compararlos con los bytes del encabezado mágico para JPEG.
Tomé la respuesta de Semicolon y la convertí a 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
Un método que admite 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();
}
}
}
aquí está mi imagen de verificación. 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 conozco 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;
}
en caso de que necesite la lectura de datos para otras operaciones y / o para otros tipos de archivos (PSD, por ejemplo), más adelante, el uso de la función Image.FromStream
no es necesariamente una buena idea.
Utilizando 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 está utilizando WPF 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;
}
Debes 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, arrojando un resultado incorrecto, y si elimina imágenes después de este paso, posiblemente estaría eliminando Buenos.