web-dev-qa-db-fra.com

Valider l'image du fichier en C #

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);
50
SemiColon

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.

  • Décalage 0 (deux octets): JPEG SOI (hex FFD8)
  • Décalage 2 (deux octets): largeur de l'image en pixels
  • Décalage 4 (deux octets): hauteur de l'image en pixels
  • Décalage 6 (octet): nombre de composants (1 = niveaux de gris, 3 = RVB)

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 :)

22
FlySwat

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;
}
67
Alex

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.

33
MusiGenesis

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;
}
18
SemiColon

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.

12
Troy Howard

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.

6
David Boike

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();
        }
    }
}
3
Paulo

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
2
TarmoPikaro

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
1
ray

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.

0
lorddarq

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.

0
Enrico Murru

Vous pouvez lire les premiers octets du flux et les comparer aux octets d'en-tête magiques pour JPEG.

0
Quantenmechaniker