Как я могу определить, является ли файл файлом изображения в .NET?

Я не хочу полагаться на расширение файла. Мне не важно, какой это тип изображения (.jpg, .png и т. Д.), Я просто хочу знать, является ли файл изображением или нет. Я бы предпочел по возможности не использовать библиотеки DLL, отличные от .NET.

Я знаю, как это сделать лучше всего:

bool isImageFile;
try
{
    Image.FromFile(imageFile).Dispose();
    isImageFile = true;
}
catch (OutOfMemoryException)
{
    isImageFile = false;
}

Как указано здесь: http://msdn.microsoft.com/en-us/library/stf701f5.aspx, Image.FromFile() выдает OutOfMemoryException, если файл недопустимого формата изображения. Использование вышеуказанного дает мне именно тот результат, который я хочу, однако я бы предпочел не использовать его по следующим причинам:

  • Я считаю, что использование try-catch для нормального выполнения программы является плохой практикой по соображениям производительности.
  • Image.FromFile() загружает в память весь файл изображения (если это файл изображения). Я предполагаю, что это расточительно, потому что мне нужен только тип файла, и на этом этапе моего кода мне не нужно выполнять какие-либо дальнейшие манипуляции с изображениями.
  • Мне не нравится ловить OutOfMemoryExceptions, потому что что, если есть РЕАЛЬНАЯ проблема нехватки памяти, и моя программа проглатывает ее и продолжает работать?

Есть ли более эффективные способы сделать это? Или все ли / все мои опасения, перечисленные выше, являются необоснованными?

Изменить: с тех пор, как я получил здесь ответы, я теперь знаю три решения:

  1. Load the whole image in memory via Image.FromFile() and a try-catch.
    • Pros: Does a deeper check against the image files contents; covers many image types.
    • Минусы: самый медленный; накладные расходы от try-catch и загрузки полного файла изображения в память; потенциальная опасность от "реального" OutOfMemoryException.
  2. Check the header bytes of the image file.
    • Pros: Quick, low memory usage.
    • Минусы: потенциально хрупкий; нужно программировать для каждого типа файла.
  3. Check the file extension.
    • Pros: Quickest; simplest.
    • Минусы: работает не во всех ситуациях; проще всего ошибиться.

(Я не вижу явного «победителя», так как могу представить ситуацию, в которой каждый из них будет подходящим. Для целей моего приложения проверка типа файла происходит достаточно редко, чтобы проблемы с производительностью метода 1 не были проблемой.)


person Daryl    schedule 20.02.2012    source источник
comment
См. Также: stackoverflow.com/questions/670546 /   -  person Daryl    schedule 06.03.2013
comment
См. Также № 2: http://stackoverflow.com/questions/210650/validate-image-from-file-in-c-sharp   -  person Daryl    schedule 06.03.2013
comment
См. Также № 3: stackoverflow.com/questions / 55869 /   -  person Daryl    schedule 06.03.2013


Ответы (6)


  1. Вы заметите снижение производительности из-за исключений, только если вы их постоянно выбрасываете. Поэтому, если ваша программа не ожидает увидеть много недопустимых изображений (сотни в секунду), вы не должны замечать накладных расходов на обработку исключений.
  2. Это действительно единственный способ узнать, является ли изображение полным или поврежденным. Вы можете проверить заголовки, как рекомендуют другие люди, но это проверяет только правильность первых нескольких байтов, все остальное может быть мусором. Достаточно ли это, зависит от требований вашего приложения. Простого чтения заголовка может быть достаточно для вашего случая использования.
  3. Да, это довольно плохой дизайн со стороны команды BCL. Если вы загружаете много больших изображений, вы вполне можете столкнуться с реальной ситуацией OOM в куче больших объектов. Насколько мне известно, два исключения невозможно различить.
person Jared Shaver    schedule 20.02.2012
comment
Спасибо за информацию. В своей ситуации я решил, что снижение производительности несущественно. - person Daryl; 20.02.2012

Если вы будете поддерживать только несколько популярных форматов изображений, вы можете просто прочитать первые несколько байтов файла, чтобы определить тип на основе Magic Number

Примеры из предоставленной ссылки:

  • Файлы изображений GIF имеют код ASCII для «GIF89a» (47 49 46 38 39 61) или «GIF87a» (47 49 46 38 37 61).
  • Файлы изображений JPEG начинаются с FF D8 и заканчиваются FF D9. Файлы JPEG / JFIF содержат код ASCII для «JFIF» (4A 46 49 46) в виде строки с завершающим нулем.
  • Файлы изображений PNG начинаются с 8-байтовой подписи, которая идентифицирует файл как файл PNG и позволяет обнаруживать общие проблемы передачи файлов: \ 211 P N G \ r \ n \ 032 \ n (89 50 4E 47 0D 0A 1A 0A).
person tawman    schedule 20.02.2012
comment
Спасибо за информацию; Я не знал, что это можно сделать. Я написал реализацию и разместил ее здесь как вики. - person Daryl; 20.02.2012

Я могу понять ваши опасения, но если вы посмотрите на источник метода Image.FromFile, это просто оболочка для вызовов GDI +, поэтому, к сожалению, вы ничего не можете сделать, поскольку, как я вижу, этот странный выбор исключения (OutOfMemoryException) был выполнен в GDI +

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

Может быть, вам стоит подумать, действительно ли вам нужен метод isImageFile? Обнаруживать файлы изображений по расширению, это будет намного быстрее, и в случае сбоя загрузки из файла возникнет исключение, чтобы вы могли справиться с этим, когда вам действительно нужно загрузить изображение.

person Antonio Bakula    schedule 20.02.2012

Выбрав путь проверки заголовка файла, я написал такую ​​реализацию:

public static ImageType GetFileImageTypeFromHeader(string file)
    {
        byte[] headerBytes;
        using (FileStream fileStream = new FileStream(file, FileMode.Open))
        {
            const int mostBytesNeeded = 11;//For JPEG

            if (fileStream.Length < mostBytesNeeded)
                return ImageType.Unknown;

            headerBytes = new byte[mostBytesNeeded];
            fileStream.Read(headerBytes, 0, mostBytesNeeded);
        }

        //Sources:
        //http://stackoverflow.com/questions/9354747
        //http://en.wikipedia.org/wiki/Magic_number_%28programming%29#Magic_numbers_in_files
        //http://www.mikekunz.com/image_file_header.html

        //JPEG:
        if (headerBytes[0] == 0xFF &&//FF D8
            headerBytes[1] == 0xD8 &&
            (
             (headerBytes[6] == 0x4A &&//'JFIF'
              headerBytes[7] == 0x46 &&
              headerBytes[8] == 0x49 &&
              headerBytes[9] == 0x46)
              ||
             (headerBytes[6] == 0x45 &&//'EXIF'
              headerBytes[7] == 0x78 &&
              headerBytes[8] == 0x69 &&
              headerBytes[9] == 0x66)
            ) &&
            headerBytes[10] == 00)
        {
            return ImageType.JPEG;
        }
        //PNG 
        if (headerBytes[0] == 0x89 && //89 50 4E 47 0D 0A 1A 0A
            headerBytes[1] == 0x50 &&
            headerBytes[2] == 0x4E &&
            headerBytes[3] == 0x47 &&
            headerBytes[4] == 0x0D &&
            headerBytes[5] == 0x0A &&
            headerBytes[6] == 0x1A &&
            headerBytes[7] == 0x0A)
        {
            return ImageType.PNG;
        }
        //GIF
        if (headerBytes[0] == 0x47 &&//'GIF'
            headerBytes[1] == 0x49 &&
            headerBytes[2] == 0x46)
        {
            return ImageType.GIF;
        }
        //BMP
        if (headerBytes[0] == 0x42 &&//42 4D
            headerBytes[1] == 0x4D)
        {
            return ImageType.BMP;
        }
        //TIFF
        if ((headerBytes[0] == 0x49 &&//49 49 2A 00
             headerBytes[1] == 0x49 &&
             headerBytes[2] == 0x2A &&
             headerBytes[3] == 0x00)
             ||
            (headerBytes[0] == 0x4D &&//4D 4D 00 2A
             headerBytes[1] == 0x4D &&
             headerBytes[2] == 0x00 &&
             headerBytes[3] == 0x2A))
        {
            return ImageType.TIFF;
        }

        return ImageType.Unknown;
    }
    public enum ImageType
    {
        Unknown,
        JPEG,
        PNG,
        GIF,
        BMP,
        TIFF,
    }

Я поместил это в служебный / вспомогательный класс вместе с методами: GetFileImageTypeFromFullLoad() и GetFileImageTypeFromExtension(). Первый использует мой вышеупомянутый подход Image.FromFile, а второй просто проверяет расширение файла. Я планирую использовать все три в зависимости от требований ситуации.

person Community    schedule 20.02.2012
comment
Вот еще один, с которым я столкнулся. Он делает то же самое, но немного меньше: - person Justin; 28.08.2014
comment
@Panuvin, вы забыли добавить ссылку? - person Daryl; 29.08.2014

Сначала используйте метод System.IO.Path.GetExtension (), чтобы проверить, является ли расширение типом изображения. Затем, если вы хотите закончить, вы можете проверить заголовки в файле.

person Four_0h_Three    schedule 20.02.2012
comment
Что делать, если продления нет? - person ; 13.03.2016

вот тот, который использует подписи в Gdi +:

public static ImageCodecInfo DetectCodec(Stream stream)
{
    var ib = 0;

    var rgCodecs = ImageCodecInfo.GetImageDecoders();
    for (var cCodecs = rgCodecs.Length; cCodecs > 0; )
    {
        var b = stream.ReadByte();
        if (b == -1)
            return null;    // EOF

        for (int iCodec = cCodecs - 1; iCodec >= 0; iCodec--)
        {
            var codec = rgCodecs[iCodec];
            for (int iSig = 0; iSig < codec.SignaturePatterns.Length; iSig++)
            {
                var mask = codec.SignatureMasks[iSig];
                var patt = codec.SignaturePatterns[iSig];

                if (ib >= patt.Length)
                    return codec;

                if ((b & mask[ib]) != patt[ib])
                {
                    rgCodecs[iCodec] = rgCodecs[--cCodecs];
                    break;
                }
            }
        }

        ib++;
    }

    return null;
}
person Spongman    schedule 17.01.2015