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

Я не хочу полагаться на расширение файла.Мне все равно, какой тип изображения (.jpg, .png и т. Д.), Я просто хочу знать, является ли файл изображением или нет.Я предпочел бы не использовать никакие не .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-catches длянормальное выполнение программы является плохой практикой по соображениям производительности.
  • Image.FromFile() загружает весь файл изображения (если это файл изображения) в память.Я полагаю, это расточительно, потому что мне нужен только тип файла, и мне не нужно больше манипулировать изображениями на этом этапе в моем коде.
  • Я не люблю перехватывать OutOfMemoryException с, потому что, если существует РЕАЛЬНОЕпроблема нехватки памяти, и моя программа глотает ее и продолжает работать?

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

Редактировать: С момента получения ответов здесь, это три решения Теперь я знаю:

  1. Загрузка всего изображения в память с помощью Image.FromFile() и try-catch.
    • Плюсы : Проводит более глубокую проверку содержимого файлов изображений;охватывает много типов изображений.
    • Минусы : медленный;накладные расходы от try-catch и загрузки полного файла изображения в память;потенциальная опасность от перехвата «настоящего» исключения OutOfMemoryException.
  2. Проверьте байты заголовка файла изображения.
    • Плюсы : Быстрое использование памяти.
    • Минусы : потенциально хрупкие;необходимо программировать для каждого типа файла.
  3. Проверьте расширение файла.
    • Плюсы : Самый быстрый;самое простое.
    • Минусы : Не работает во всех ситуациях;проще всего ошибиться.

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

Ответы [ 6 ]

11 голосов
/ 20 февраля 2012

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

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

  • Файлы изображений 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 PNG \ r \ n \ 032 \ n (89 50 4E 47 0D 0A 1A 0A ).
8 голосов
/ 20 февраля 2012
  1. Вы заметите снижение производительности только из исключений, если будете постоянно их бросать.Поэтому, если ваша программа не ожидает увидеть много недопустимых изображений (сотни в секунду), вы не должны замечать накладных расходов на обработку исключений.
  2. Это действительно единственный способ определить, является ли изображение полным изображением или повреждено.Вы можете проверить заголовки, как рекомендуют другие люди, но это проверяет только, чтобы убедиться, что первые несколько байтов верны, все остальное может быть мусором.Является ли это достаточно хорошим или нет, зависит от требований вашего приложения.Просто чтение заголовка может быть достаточно для вашего случая использования.
  3. Да, это довольно плохой дизайн со стороны команды BCL.Если вы загружаете много больших изображений, вы вполне можете столкнуться с реальной ситуацией OOM в куче больших объектов.Насколько я знаю, нет никакого способа разграничить два исключения.
2 голосов
/ 20 февраля 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 подход, а второй просто проверяет расширение файла. Я планирую использовать все три в зависимости от требований ситуации.

2 голосов
/ 20 февраля 2012

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

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

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

1 голос
/ 17 января 2015

вот тот, который использует подписи в 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;
}
1 голос
/ 20 февраля 2012

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

...