57 votes

Valider une image à partir d'un fichier en C#

Je suis en train de charger 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 du fichier image.jpg.

Je cherche une fonction qui validera une image à partir 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);

3 votes

Pourquoi ne pas envelopper ce code dans un bloc try...catch, et s'il lève cette exception, vous pouvez le considérer comme "invalide" ? Certes, il s'agit d'une heuristique naïve, mais elle fait l'affaire. Toute autre méthode devra toujours ouvrir le fichier, donc vous ne gagnerez pas beaucoup en performances, selon moi.

0 votes

0 votes

Voir aussi, pour une méthode alternative : stackoverflow.com/q/2053662/2181514

90voto

Alex Points 2185

Voici mon contrôle d'image. Je ne peux pas me fier aux extensions de fichiers et je dois vérifier le format par moi-même. Je charge les BitmapImages dans WPF à partir de tableaux d'octets et je ne connais pas le format à l'avance. WPF détecte bien le format mais ne vous indique pas le format d'image des objets BitmapImage (du moins, je ne connais pas de propriété pour cela). Et je ne veux pas recharger 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;
}

0 votes

Le code ci-dessus échouait pour un fichier PNG particulier. Quand j'ai vérifié, les 4 premiers octets contenaient {80, 75, 3, 4} au lieu de la séquence que vous avez mentionnée. L'image peut être ouverte par des visualisateurs/éditeurs normaux. Qu'est-ce qui se passe ?

0 votes

J'ai un JPEG avec 255,216,255,237 donc cela ne fonctionne pas.

1 votes

il suffit d'ajouter cette séquence d'octets au code lorsque cela est valable pour un jpeg et le code fonctionnera bien.

35voto

MusiGenesis Points 49273

Utilisation 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 utiliser 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 lancerait OutOfMemoryException parce que le système a manqué de ressources, et non parce que l'image est corrompue, ce qui donne un résultat incorrect. Si vous supprimez des images après cette étape, vous risquez de supprimer de bonnes images.

0 votes

Merci :) . Je pensais faire cela, mais je me demandais s'il y avait un moyen de le faire qui soit déjà intégré au cadre .NET. Comme personne d'autre n'a mentionné de fonctions intégrées dans le cadre .NET pour faire cela, je pense que ce serait une bonne solution.

2 votes

Vous devriez probablement attraper OutOfMemoryException, qui est l'exception documentée déclenchée si le format du fichier n'est pas valide. Cela signifie que vous laisseriez l'exception FileNotFoundException se propager à l'appelant.

0 votes

Je n'avais pas réalisé que c'était l'exception documentée pour un fichier image invalide. J'ai juste supposé qu'il pouvait y avoir différentes exceptions lancées en fonction de ce qui n'allait pas avec le fichier. Merci.

23voto

FlySwat Points 61945

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.

  • Offset 0 (deux octets) : Marqueur JPEG SOI (FFD8 hex)
  • Offset 2 (deux octets) : Largeur de l'image en pixels
  • Offset 4 (deux octets) : Hauteur de l'image en pixels
  • Offset 6 (octet) : Nombre de composants (1 = niveaux de gris, 3 = RGB)

Il y a quelques autres choses après ça, mais ce n'est pas important.

Vous pouvez ouvrir le fichier en utilisant un flux binaire, et lire ces données initiales, et vous assurer que OffSet 0 est 0, et que OffSet 6 est soit 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 :)

0 votes

J'aurais lu l'en-tête du fichier et l'aurais comparé à un tableau d'en-têtes de fichiers d'images supportées par .NET. Un jour, je coderai cela et le posterai comme solution pour ceux qui en auraient besoin à l'avenir.

1 votes

La simple lecture des en-têtes ne garantit pas que le fichier est valide et ne lèvera pas une exception lorsqu'il sera ouvert par Image.FromFile().

3 votes

Non, mais je n'ai pas prétendu que c'était le cas.

21voto

SemiColon Points 1053

Eh bien, je me suis lancé 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 ne vérifie que les fichiers GIF, BMP, JPG et PNG. Vous pouvez facilement ajouter d'autres types en ajoutant un en-tête à 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 votes

Pas tout à fait. Si imageStream.Read lève une exception, vous ne le fermez pas pour autant. Le mieux est de placer une instruction using autour de l'instanciation du flux.

6 votes

@Joe Je ne suis pas d'accord. Il ne devrait pas fermer ou disposer du flux dans cette fonction. Cette fonction n'a pas créé le flux, et ne devrait donc pas avoir de comportements inattendus. Aussi . En cas de succès, Image.FromStream consommera le flux (qui peut être en lecture seule, et ne peut pas être réinitialisé), ce qui signifie qu'une lecture ultérieure du flux échouera puisque le flux a déjà été consommé. De plus, en cas de succès, l'image est chargée (ce qui est très coûteux) et est ensuite éliminée immédiatement. Si cette méthode renvoie vrai, il est probable que l'appelant charge l'image à la ligne suivante. C'est donc un double travail.

0 votes

@Troy, je suis d'accord. Il serait préférable que cette méthode prenne un tableau d'octets ou un objet similaire qui ne soit pas affecté par la méthode, d'autant plus qu'il est statique.

14voto

Troy Howard Points 1798

Vous pouvez faire un typage approximatif en reniflant l'en-tête.

Cela signifie que chaque format de fichier que vous mettez en œuvre devra avoir un en-tête identifiable...

JPEG : Les 4 premiers octets sont FF D8 FF E0 (en fait juste les deux premiers octets le feraient pour un jpeg non jfif, plus d'info ici ).

GIF : Les 6 premiers octets sont soit "GIF87a", soit "GIF89a" (plus d'informations). ici )

PNG : Les 8 premiers octets sont : 89 50 4E 47 0D 0A 1A 0A (plus d'informations) 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 n'importe quel format graphique qui vous intéresse et ajouter des éléments à ceux qu'il gère si nécessaire. Ce que cela ne fera pas, c'est vous dire si le fichier est une version valide de ce type, mais cela vous donnera un indice sur "image not image ?". Il peut toujours s'agir d'une image corrompue ou incomplète, et donc planter à l'ouverture, donc un try catch autour de l'appel .FromFile est toujours nécessaire.

6 votes

hmm quatre personnes ont répondu pendant que je tapais ça et que je rassemblais les liens. C'est un endroit très occupé.

0 votes

Veuillez corriger pour TIFF les 4 premiers octets sont II* (49 49 42 00) ou MM* (4D 4D 00 42).

0 votes

Pour le JPEG, les 3 premiers octets feront l'affaire, FFD8 est un marqueur SOI et FF ?? est le marqueur APP où ? ? est généralement E0. Donc, pour les jpeg non jfif, 3 octets FFD8FF feront l'affaire.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X