Je charge une image à partir d'un fichier et je veux savoir comment valider l'image avant qu'elle ne soit entièrement lue à partir du fichier.
string filePath = "image.jpg";
Image newImage = Image.FromFile(filePath);
Le problème se produit lorsque image.jpg n'est pas vraiment un jpg. Par exemple, si je crée un fichier texte vide et que je le renomme en image.jpg, une exception OutOfMemory sera levée lors du chargement de image.jpg.
Je recherche une fonction qui validera une image en fonction d'un flux ou d'un chemin de fichier de l'image.
Exemple de prototype de fonction
bool IsValidImage(string fileName);
bool IsValidImage(Stream imageStream);
Les JPEG n'ont pas de définition d'en-tête formelle, mais ils ont une petite quantité de métadonnées que vous pouvez utiliser.
Il y a quelques autres choses après cela, mais elles ne sont pas importantes.
Vous pouvez ouvrir le fichier à l'aide d'un flux binaire, lire ces données initiales et vous assurer que OffSet 0 est 0 et OffSet 6 est 1,2 ou 3.
Cela vous donnerait au moins un peu plus de précision.
Ou vous pouvez simplement piéger l'exception et passer à autre chose, mais je pensais que vous vouliez un défi :)
voici ma vérification d'image. Je ne peux pas compter sur les extensions de fichiers et dois vérifier le format par moi-même. Je charge BitmapImages dans WPF à partir de tableaux d'octets et je ne connais pas le format à l'avance. WPF détecte le format correctement mais ne vous indique pas le format d'image des objets BitmapImage (au moins je ne connais pas de propriété pour cela). Et je ne veux pas charger à nouveau l'image avec System.Drawing uniquement pour détecter le format. Cette solution est rapide et fonctionne bien pour moi.
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;
}
tilisation 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;
}
Sinon, si vous êtes en utilisant WPF vous pouvez faire ce qui suit:
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;
}
Vous devez libérer l'image créée. Sinon, lorsque vous appelez cette fonction un grand nombre de fois, cela produira OutOfMemoryException car le système n'a plus de ressources, et non pas parce que l'image est corrompue, ce qui donne un résultat incorrect, et si vous supprimez des images après cette étape , vous pourriez potentiellement supprimer les bons.
Eh bien, je suis allé de l'avant et j'ai codé un ensemble de fonctions pour résoudre le problème. Il vérifie d'abord l'en-tête, puis tente de charger l'image dans un bloc try/catch. Il vérifie uniquement les fichiers GIF, BMP, JPG et PNG. Vous pouvez facilement ajouter plus de types en ajoutant un en-tête aux 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;
}
Vous pouvez effectuer une frappe approximative en reniflant l'en-tête.
Cela signifie que chaque format de fichier que vous implémentez devra avoir un en-tête identifiable ...
JPEG: les 4 premiers octets sont FF D8 FF E0 (en fait, seuls les deux premiers octets le feraient pour le format jpeg non jfif, plus d'informations ici ).
GIF: les 6 premiers octets sont "GIF87a" ou "GIF89a" (plus d'infos ici )
PNG: les 8 premiers octets sont: 89 50 4E 47 0D 0A 1A 0A (plus d'infos ici )
TIFF: Les 4 premiers octets sont: II42 ou MM42 (plus d'infos ici )
etc ... vous pouvez trouver des informations d'en-tête/format pour à peu près tous les formats graphiques qui vous intéressent et ajouter aux choses qu'il gère selon les besoins. Ce que cela ne fera pas, c'est de vous dire si le fichier est une version valide de ce type, mais cela vous donnera un indice sur "image pas image?". Il pourrait toujours s'agir d'une image corrompue ou incomplète, et donc se bloquer lors de l'ouverture, donc une tentative de capture autour de l'appel .FromFile est toujours nécessaire.
Cela devrait faire l'affaire - vous n'avez pas à lire les octets bruts de l'en-tête:
using(Image test = Image.FromFile(filePath))
{
bool isJpeg = (test.RawFormat.Equals(ImageFormat.Jpeg));
}
Bien sûr, vous devez également intercepter l'OutOfMemoryException, ce qui vous sauvera si le fichier n'est pas du tout une image.
Et, ImageFormat a des éléments prédéfinis pour tous les autres principaux types d'images pris en charge par GDI +.
Remarque, vous devez utiliser .Equals () et non == sur les objets ImageFormat (ce n'est pas une énumération) car l'opérateur == n'est pas surchargé pour appeler la méthode Equals.
Une méthode qui prend également en charge Tiff et Jpeg
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();
}
}
}
J'ai remarqué quelques problèmes avec toutes les fonctions ci-dessus. Tout d'abord - Image.FromFile ouvre l'image donnée et ensuite provoquera une erreur de fichier ouvert quiconque veut ouvrir le fichier image donné pour une raison quelconque. Même l'application elle-même - j'ai donc changé d'utilisation d'Image.FromStream.
Après avoir basculé l'api - les changements de type d'exception d'OutOfMemoryException à ArgumentException pour une raison peu claire pour moi. (Probablement un bug du framework .net?)
De plus, si .net ajoutera plus de supports de format de fichier d'image qu'actuellement, nous vérifierons par fonction - il est logique d'essayer d'abord de charger l'image si seulement en cas d'échec - seulement après cela pour signaler une erreur.
Donc, mon code ressemble maintenant à ceci:
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;
}
Où:
/// <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
J'ai pris la réponse de Semicolon et converti 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
au cas où vous auriez besoin que ces données soient lues pour d'autres opérations et/ou pour d'autres types de fichiers (PSD par exemple), plus tard, puis en utilisant le Image.FromStream
la fonction n'est pas nécessairement une bonne idée.
Je créerais une méthode comme:
Image openImage(string filename);
dans lequel je gère l'exception. Si la valeur renvoyée est Null, il existe un nom/type de fichier non valide.
Vous pouvez lire les premiers octets du flux et les comparer aux octets d'en-tête magiques pour JPEG.