определить, является ли файл изображением

Я перебираю каталог и копирую все файлы. Прямо сейчас я делаю string.EndsWith проверки на ".jpg" или ".png" и т. д. .

Есть ли более элегантный способ определить, является ли файл изображением (любого типа изображения) без хакерской проверки, как указано выше?


person leora    schedule 22.03.2009    source источник
comment
Есть ли причина, по которой расширение недостаточно хорошее?   -  person peterchen    schedule 22.03.2009
comment
См. также: stackoverflow.com/questions/9354747/   -  person Daryl    schedule 06.03.2013
comment
@peterchen да, возможно, файл является изображением, но каким-то образом переименованным во что-то другое   -  person Mehdi Dehghani    schedule 26.02.2020


Ответы (13)


Проверьте файл на наличие известного заголовка. . (Информация из ссылки также упоминается в этом ответе)

Первые восемь байтов файла PNG всегда содержат следующие (десятичные) значения: 137 80 78 71 13 10 26 10

person Mitch Wheat    schedule 22.03.2009
comment
Ничего себе, Clarion все еще здесь! Вот это сюрприз - я прорезал один или два зуба на Clarion где-то в 95-м. - person ProfK; 22.03.2009
comment
Обратите внимание, что символы со второго по шестой имеют формат PNG\r\n - person Ben Voigt; 04.06.2013
comment
Извините, но ваша ссылка больше не работает. У меня ошибка 403. - person H. Pauwelyn; 30.10.2015
comment
Да ссылка битая :( - person Wahid Bitar; 26.07.2016
comment
Содержание ссылки, изначально опубликованное в июле 2008 г., заархивировано на WayBackMachine - person Roberto; 13.11.2017
comment
Как насчет изображений PNG и WEBP? - person android developer; 09.06.2020

Ознакомьтесь с System.IO.Path.GetExtension.

Вот быстрый пример.

public static readonly List<string> ImageExtensions = new List<string> { ".JPG", ".JPE", ".BMP", ".GIF", ".PNG" };

private void button_Click(object sender, RoutedEventArgs e)
{
    var folder = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
    var files = Directory.GetFiles(folder);
    foreach(var f in files)
    {
        if (ImageExtensions.Contains(Path.GetExtension(f).ToUpperInvariant()))
        {
            // process image
        }
    }
}
person bendewey    schedule 22.03.2009
comment
Он сказал, что проверяет, является ли строка.EndsWith из того, что я собрал - person bendewey; 22.03.2009
comment
да, но это все еще сравнение строк. Что, если я переименую файл .jpf в .txt? - person Mitch Wheat; 22.03.2009
comment
Есть два возможных толкования вопроса; либо «EndsWith» является хакерским (в этом случае этот ответ — это то, что хочет ОП), либо «использование имени файла» является хакерским, и в этом случае ответ @MitchWheat — это то, что хочет ОП. Я предпочитаю Митча, но проголосовал за обоих. - person Brian; 22.03.2009
comment
Если вы используете расширение, обязательно преобразуйте его в нижний регистр, прежде чем выполнять сравнения. (Например, многие цифровые камеры создают файлы .JPG.) - person Thomas; 22.03.2009

System.Web.MimeMapping.GetMimeMapping(filename).StartsWith("image/");

MimeMapping.GetMimeMapping дает следующие результаты:

  • файл.jpg: изображение/jpeg
  • файл.gif: изображение/gif
  • файл.jpeg: изображение/jpeg
  • файл.png: изображение/png
  • файл.bmp: изображение/bmp
  • файл.tiff: изображение/tiff
  • file.svg: приложение/октет-поток

file.svg, не возвращающий тип изображения/MIME, работает в большинстве случаев, потому что вы, вероятно, не собираетесь обрабатывать векторное изображение, как скалярное изображение. При проверке MIME-типа помните, что SVG имеет стандартный MIME-тип image/svg+xml, даже если GetMimeMapping его не возвращает.

person Jeremy Cook    schedule 08.06.2016
comment
если расширение файла намеренно изменено, например. из .jpg в .txt это не удастся, т.е. вывод System.Web.MimeMapping.GetMimeMapping(имя файла) будет текстовым/обычным - person amyn; 21.10.2016

Это просмотрит первые несколько байтов файла и определит, является ли это изображением.

using System.Collections.Generic;
using System.IO;
using System.Linq;

public static class Extension
{
    public static bool IsImage(this Stream stream)
    {
        stream.Seek(0, SeekOrigin.Begin);

        List<string> jpg = new List<string> { "FF", "D8" };
        List<string> bmp = new List<string> { "42", "4D" };
        List<string> gif = new List<string> { "47", "49", "46" };
        List<string> png = new List<string> { "89", "50", "4E", "47", "0D", "0A", "1A", "0A" };
        List<List<string>> imgTypes = new List<List<string>> { jpg, bmp, gif, png };

        List<string> bytesIterated = new List<string>();

        for (int i = 0; i < 8; i++)
        {
            string bit = stream.ReadByte().ToString("X2");
            bytesIterated.Add(bit);

            bool isImage = imgTypes.Any(img => !img.Except(bytesIterated).Any());
            if (isImage)
            {
                return true;
            }
        }

        return false;
    }
}

Редактировать

Я внес несколько изменений в приведенное выше, чтобы вы могли добавлять свои собственные изображения, если вам это нужно, а также удалил коллекции, в которых изначально не было необходимости. Я также добавил перегрузку, принимающую параметр out типа string, присваивая значение типу изображения, из которого состоит поток.

public static class Extension
{
    static Extension()
    {
        ImageTypes = new Dictionary<string, string>();
        ImageTypes.Add("FFD8","jpg");
        ImageTypes.Add("424D","bmp");
        ImageTypes.Add("474946","gif");
        ImageTypes.Add("89504E470D0A1A0A","png");
    }
    
    /// <summary>
    ///     <para> Registers a hexadecimal value used for a given image type </para>
    ///     <param name="imageType"> The type of image, example: "png" </param>
    ///     <param name="uniqueHeaderAsHex"> The type of image, example: "89504E470D0A1A0A" </param>
    /// </summary>
    public static void RegisterImageHeaderSignature(string imageType, string uniqueHeaderAsHex)
    {
        Regex validator = new Regex(@"^[A-F0-9]+$", RegexOptions.CultureInvariant);
    
        uniqueHeaderAsHex = uniqueHeaderAsHex.Replace(" ", "");
        
        if (string.IsNullOrWhiteSpace(imageType))         throw new ArgumentNullException("imageType");
        if (string.IsNullOrWhiteSpace(uniqueHeaderAsHex)) throw new ArgumentNullException("uniqueHeaderAsHex");
        if (uniqueHeaderAsHex.Length % 2 != 0)            throw new ArgumentException    ("Hexadecimal value is invalid");
        if (!validator.IsMatch(uniqueHeaderAsHex))        throw new ArgumentException    ("Hexadecimal value is invalid");
        
        ImageTypes.Add(uniqueHeaderAsHex, imageType);
    }
    
    private static Dictionary<string, string> ImageTypes;

    public static bool IsImage(this Stream stream)
    {
        string imageType;
        return stream.IsImage(out imageType);
    }
    
    public static bool IsImage(this Stream stream, out string imageType)
    {
        stream.Seek(0, SeekOrigin.Begin);
        StringBuilder builder = new StringBuilder();
        int largestByteHeader = ImageTypes.Max(img => img.Value.Length);
        
        for (int i = 0; i < largestByteHeader; i++)
        {
            string bit = stream.ReadByte().ToString("X2");
            builder.Append(bit);
            
            string builtHex = builder.ToString();
            bool isImage = ImageTypes.Keys.Any(img => img == builtHex);
            if (isImage)
            {
                imageType = ImageTypes[builder.ToString()];
                return true;
            }
        }
        imageType = null;
        return false;
    }
}
person Aydin    schedule 26.08.2014
comment
не практично, так как вам не хватает многих возможных типов изображений. - person scott_f; 09.02.2015
comment
Несколько опечаток: должно быть return IsImage(stream, out imageType); вместо возврата stream.IsImage(out imageType); Должен быть int самый большойByteHeader = ImageTypes.Max(img => img.Key.Length) вместо int самый большойByteHeader = ImageTypes.Max(img => img.Value.Length) - person an phu; 29.10.2015
comment
Реализуйте то, что вы только что написали, и посмотрите, скомпилируется ли оно. - person Aydin; 29.10.2015
comment
В вашем редактировании есть небольшая ошибка в строке int largestByteHeader = ImageTypes.Max(img => img.Value.Length); - длина должна быть разделена на 2, так как каждый символ в шестнадцатеричной строке стоит только 4 бита, а не 8, и вы используете результат этого оператора, как будто это количество байтов в заголовок. Возьмем, к примеру, PNG, размер заголовка составляет 8 байтов, но ваш код ошибочно вычисляет его как 16 байтов. Он по-прежнему работает, так как просто сканирует больше, чем необходимо, в случаях, не связанных с изображениями, но это делает код запутанным. - person chillNZ; 06.08.2018
comment
Также @an-phu был прав в том, что img.Value.Length должно быть img.Key.Length (хотя вы были правы в том, что метод расширения не нужно менять). - person chillNZ; 06.08.2018
comment
Спасибо @Aydin. Это решение сработало для меня. Просто любопытно, как вы получили эти магические числа для каждого типа изображения. Я хотел бы добавить JPEG и SVG. Кроме того, могу ли я использовать ту же логику для документов и видео? Я хотел бы проверить, имеет ли файл какой-либо из этих форматов - pdf, txt, doc, docx, mp4 и mov. Заранее спасибо. - person Frenz; 03.11.2020

Мы можем использовать классы изображений и графики из пространства имен System.Drawing; делать нашу работу. Если код работает без ошибок, то это изображение, иначе — нет. То есть пусть платформа DotNet сделает всю работу за нас. Код -

public string CheckFile(file)
{
    string result="";
    try
    {
        System.Drawing.Image imgInput = System.Drawing.Image.FromFile(file);
        System.Drawing.Graphics gInput = System.Drawing.Graphics.fromimage(imgInput);  
        Imaging.ImageFormat thisFormat = imgInput.RawFormat;   
        result="It is image";        
    }
    catch(Exception ex)
    {
        result="It is not image"; 
    }
    return result;
}
person yogihosting    schedule 05.07.2015
comment
Добавьте этот код в пробный раздел: imgInput.Dispose(); gInput.Dispose(); . Разве что дескриптор файла будет открыт и другие процессы не смогут его использовать. - person Beginner; 03.08.2017
comment
Будьте осторожны: пока это работает, оно будет использовать системные ресурсы и память. Хорошо для нескольких проверок изображений здесь и там, но не для веб-среды, где люди могут загружать изображения любого размера. - person o_o; 17.11.2017

Если вам нужен быстрый способ проверки файла изображения до того, как он будет полностью прочитан из файла, помимо сравнения расширения файла, вы можете просто проверить его заголовок в поисках подписи файла (следующий код IsValidImageFile() проверяет наличие BMP, GIF87a, GIF89a, PNG, TIFF, JPEG)

    /// <summary>
    /// Reads the header of different image formats
    /// </summary>
    /// <param name="file">Image file</param>
    /// <returns>true if valid file signature (magic number/header marker) is found</returns>
    private bool IsValidImageFile(string file)
    {
        byte[] buffer = new byte[8];
        byte[] bufferEnd = new byte[2];

        var bmp = new byte[] { 0x42, 0x4D };               // BMP "BM"
        var gif87a = new byte[] { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 };     // "GIF87a"
        var gif89a = new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 };     // "GIF89a"
        var png = new byte[] { 0x89, 0x50, 0x4e, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };   // PNG "\x89PNG\x0D\0xA\0x1A\0x0A"
        var tiffI = new byte[] { 0x49, 0x49, 0x2A, 0x00 }; // TIFF II "II\x2A\x00"
        var tiffM = new byte[] { 0x4D, 0x4D, 0x00, 0x2A }; // TIFF MM "MM\x00\x2A"
        var jpeg = new byte[] { 0xFF, 0xD8, 0xFF };        // JPEG JFIF (SOI "\xFF\xD8" and half next marker xFF)
        var jpegEnd = new byte[] { 0xFF, 0xD9 };           // JPEG EOI "\xFF\xD9"

        try
        {
            using (System.IO.FileStream fs = new System.IO.FileStream(file, System.IO.FileMode.Open, System.IO.FileAccess.Read))
            {
                if (fs.Length > buffer.Length)
                {
                    fs.Read(buffer, 0, buffer.Length);
                    fs.Position = (int)fs.Length - bufferEnd.Length;
                    fs.Read(bufferEnd, 0, bufferEnd.Length);
                }

                fs.Close();
            }

            if (this.ByteArrayStartsWith(buffer, bmp) ||
                this.ByteArrayStartsWith(buffer, gif87a) ||
                this.ByteArrayStartsWith(buffer, gif89a) ||
                this.ByteArrayStartsWith(buffer, png) ||
                this.ByteArrayStartsWith(buffer, tiffI) ||
                this.ByteArrayStartsWith(buffer, tiffM))
            {
                return true;
            }

            if (this.ByteArrayStartsWith(buffer, jpeg))
            {
                // Offset 0 (Two Bytes): JPEG SOI marker (FFD8 hex)
                // Offest 1 (Two Bytes): Application segment (FF?? normally ??=E0)
                // Trailer (Last Two Bytes): EOI marker FFD9 hex
                if (this.ByteArrayStartsWith(bufferEnd, jpegEnd))
                {
                    return true;
                }
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message, Lang.Lang.ErrorTitle + " IsValidImageFile()", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }

        return false;
    }

    /// <summary>
    /// Returns a value indicating whether a specified subarray occurs within array
    /// </summary>
    /// <param name="a">Main array</param>
    /// <param name="b">Subarray to seek within main array</param>
    /// <returns>true if a array starts with b subarray or if b is empty; otherwise false</returns>
    private bool ByteArrayStartsWith(byte[] a, byte[] b)
    {
        if (a.Length < b.Length)
        {
            return false;
        }

        for (int i = 0; i < b.Length; i++)
        {
            if (a[i] != b[i])
            {
                return false;
            }
        }

        return true;
    }

Проверка подписи заголовка может быть быстрой, поскольку при этом не загружается весь файл и не создаются большие объекты, особенно при обработке нескольких файлов. Но он не проверяет правильность формирования остальных данных. Для этого можно сделать второй шаг, чтобы попытаться загрузить файл в объект Image (и таким образом убедиться, что файл может отображаться и обрабатываться вашей программой).

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;
}
person Dr Yunke    schedule 06.04.2018
comment
Что такое буферКонец? Кажется, он читает с конца файла? Это не всегда возможно, если файл не из файловой системы (требуется много пропусков), не так ли? Кроме того, как насчет WEBP? А новый HEIC? - person android developer; 09.06.2020

Я использую следующий метод. Он использует встроенный декодер изображений для получения списка расширений, которые система распознает как файлы изображений, а затем сравнивает эти расширения с расширением имени файла, которое вы передаете. Возвращает простое значение TRUE/FALSE.

public static bool IsRecognisedImageFile(string fileName)
{
    string targetExtension = System.IO.Path.GetExtension(fileName);
    if (String.IsNullOrEmpty(targetExtension))
        return false;
    else
        targetExtension = "*" + targetExtension.ToLowerInvariant();

    List<string> recognisedImageExtensions = new List<string>();

    foreach (System.Drawing.Imaging.ImageCodecInfo imageCodec in System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders())
        recognisedImageExtensions.AddRange(imageCodec.FilenameExtension.ToLowerInvariant().Split(";".ToCharArray()));

    foreach (string extension in recognisedImageExtensions)
    {
        if (extension.Equals(targetExtension))
        {
            return true;
        }
    }
    return false;
}
person dylmcc    schedule 05.06.2014
comment
Это хорошо работает как частичная замена для stackoverflow.com/a/14587821/329367. - person Darren; 26.06.2015

Посмотрите, поможет ли это.

РЕДАКТИРОВАТЬ: Кроме того, может помочь Image.FromFile(....).RawFormat. Это может вызвать исключение, если файл не является изображением.

person shahkalpeshp    schedule 22.03.2009

Не совсем тот ответ, который вам нужен. Но если это Интернет, то MIME.

person Cherian    schedule 22.03.2009

Я не уверен, каков будет недостаток производительности для этого решения, но не могли бы вы выполнить некоторую функцию изображения для файла в блоке try, который потерпит неудачу и попадет в блок catch, если это не изображение?

Эта стратегия может быть не лучшим решением во всех ситуациях, но в случае, с которым я сейчас работаю, у нее есть одно важное преимущество: вы можете использовать любую функцию, которую планируете использовать для обработки изображения (если это изображение) для тестовая функция. Таким образом, вы можете протестировать все текущие типы изображений, но это также будет распространяться на будущие типы изображений без добавления этого нового расширения изображения в список поддерживаемых типов изображений.

Кто-нибудь видит недостатки этой стратегии?

person James Sumner    schedule 01.10.2012
comment
Намеренное использование try/catch нежелательно, так как исключения / могут быть дорогими (с точки зрения накладных расходов) и имеют небольшой запах кода. Это не элегантное решение. Пожалуйста, смотрите dylmcc для лучшего решения. Я использовал его, и он работает. Я не вижу недостатков в этом решении. - person scott_f; 09.02.2015

Это то, что я использую - это просто настройка ответа @dylmcc, чтобы сделать его более читабельным.

public static bool IsRecognisedImageFile(string fileName)
{
    string targetExtension = System.IO.Path.GetExtension(fileName);
    if (String.IsNullOrEmpty(targetExtension))
    {
        return false;
    }

    var recognisedImageExtensions = System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders().SelectMany(codec => codec.FilenameExtension.ToLowerInvariant().Split(';'));

    targetExtension = "*" + targetExtension.ToLowerInvariant();
    return recognisedImageExtensions.Contains(targetExtension);
}
person meataxe    schedule 25.06.2019
comment
-1. Вопрос в том, как проверить файл, а не имя файла. У вас могут быть текстовые файлы, сохраненные как .png, или действительно поврежденные файлы изображений, которые не будут открываться как изображения. - person stigzler; 07.10.2020
comment
Это один из способов интерпретировать вопрос - конечно, есть компромисс между доверием к расширению как прокси для реальности и фактическими попытками работать с изображением, используя любые подручные инструменты. - person meataxe; 08.10.2020

Это сложно. Если файл не является изображением, будет выдано исключение. Из этого мы можем проверить, является ли файл изображением или нет.

        using (Stream stream = File.OpenRead(file))
           {
               try
               {
                   using (Image sourceImage = Image.FromStream(stream, false, false))
                   {

                   }
               }
               catch (Exception x)
               {
                   if (x.Message.Contains("not valid"))
                   {
                     Console.Write("This is not a Image.");
                   }

               }
           }
person Isuru Dinusha    schedule 14.08.2019

Мой простой код

public static List<string> GetAllPhotosExtensions()
    {
        var list = new List<string>();
        list.Add(".jpg");
        list.Add(".png");
        list.Add(".bmp");
        list.Add(".gif");
        list.Add(".jpeg");
        list.Add(".tiff");
        return list;
    }

Проверьте, не является ли файл изображения

public static bool IsPhoto(string fileName)
    {
        var list = FileListExtensions.GetAllPhotosExtensions();
        var filename= fileName.ToLower();
        bool isThere = false;
        foreach(var item in list)
        {
            if (filename.EndsWith(item))
            {
                isThere = true;
                break;
            }
        }
        return isThere;     
    }
person Minute V    schedule 13.06.2018
comment
Тип файла определяется по подписи. Не по расширению файла. - person iwtu; 23.03.2021