Проверить изображение из файла в C # - PullRequest
49 голосов
/ 17 октября 2008

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

string filePath = "image.jpg";
Image newImage = Image.FromFile(filePath);

Проблема возникает, когда image.jpg на самом деле не JPG. Например, если я создам пустой текстовый файл и переименую его в image.jpg, при загрузке image.jpg будет выдано исключение OutOfMemory.

Я ищу функцию, которая будет проверять изображение с учетом потока или пути к файлу изображения.

Пример прототипа функции

bool IsValidImage(string fileName);
bool IsValidImage(Stream imageStream);

Ответы [ 12 ]

64 голосов
/ 25 февраля 2012

вот моя проверка изображения. Я не могу полагаться на расширения файлов и должен проверить формат самостоятельно. Я загружаю BitmapImages в WPF из байтовых массивов и не знаю формат заранее. WPF хорошо определяет формат, но не сообщает вам формат изображения объектов BitmapImage (по крайней мере, я не знаю свойства для этого) И я не хочу загружать изображение снова с System.Drawing только для определения формата. Это решение быстро и отлично работает для меня.

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;
}
32 голосов
/ 17 октября 2008

Использование 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;
}

В противном случае, если вы используете WPF , вы можете сделать следующее:

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

Вы должны освободить созданное изображение. Иначе, когда вы вызываете эту функцию большое количество раз, это приведет к OutOfMemoryException , потому что в системе закончились ресурсы, а не потому, что изображение повреждено, что приводит к неверному результату, и если вы удалите изображения после этого шага, потенциально вы удаляете хорошие.

23 голосов
/ 17 октября 2008

JPEG не имеют формального определения заголовка, но у них есть небольшое количество метаданных, которые вы можете использовать.

  • Смещение 0 (два байта): маркер JPEG SOI (FFD8 hex)
  • Смещение 2 (два байта): ширина изображения в пикселях
  • Смещение 4 (два байта): высота изображения в пикселях
  • Смещение 6 (байт): количество компонентов (1 = оттенки серого, 3 = RGB)

После этого есть еще пара вещей, но они не важны.

Вы можете открыть файл в двоичном потоке, прочитать исходные данные и убедиться, что OffSet 0 равен 0, а OffSet 6 равен 1,2 или 3.

Это, по крайней мере, даст вам немного больше точности.

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

18 голосов
/ 17 октября 2008

Ну, я пошел дальше и написал набор функций для решения проблемы. Сначала он проверяет заголовок, затем пытается загрузить изображение в блоке try / catch. Он проверяет только файлы GIF, BMP, JPG и PNG. Вы можете легко добавить больше типов, добавив заголовок к 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;
}
12 голосов
/ 17 октября 2008

Вы можете сделать грубый набор текста, нюхая заголовок.

Это означает, что каждый формат файла, который вы реализуете, должен иметь идентифицируемый заголовок ...

JPEG: первые 4 байта - это FF D8, FF E0 (фактически, только первые два байта сделали бы это для не jfif jpeg, больше информации здесь ).

GIF: первые 6 байтов являются либо «GIF87a», либо «GIF89a» (подробнее здесь )

PNG: первые 8 байтов: 89 50 4E 47 0D 0A 1A 0A (подробнее здесь )

TIFF: первые 4 байта: II42 или MM42 (подробнее здесь )

и т. Д. ... вы можете найти информацию заголовка / формата практически для любого графического формата, который вам нужен, и добавить к тому, что он обрабатывает по мере необходимости. Что это не сделает, так это скажет вам, является ли файл верной версией этого типа, но он даст вам подсказку об «изображении, а не изображении?». Это может быть поврежденное или неполное изображение, которое может привести к сбою при открытии, поэтому попытка перехватить вызов .FromFile по-прежнему необходима.

6 голосов
/ 29 июня 2010

Это должно сработать - вам не нужно читать необработанные байты из заголовка:

using(Image test = Image.FromFile(filePath))
{
    bool isJpeg = (test.RawFormat.Equals(ImageFormat.Jpeg));
}

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

И, ImageFormat имеет предустановленные элементы для всех других основных типов изображений, которые поддерживает GDI +.

Обратите внимание, что вы должны использовать .Equals (), а не == для объектов ImageFormat (это не перечисление), потому что оператор == не перегружен для вызова метода Equals.

3 голосов
/ 11 марта 2010

Метод, который поддерживает Tiff и 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();
        }
    }
}
1 голос
/ 09 февраля 2012

Я взял ответ с запятой и преобразовал в 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
0 голосов
/ 04 декабря 2015

Заметил пару проблем со всеми функциями выше. Прежде всего - Image.FromFile открывает данное изображение и впоследствии вызывает ошибку открытия файла, если кто-либо захочет открыть данный файл изображения по любой причине. Даже само приложение - поэтому я переключился с использованием Image.FromStream.

После переключения api - тип исключения меняется с OutOfMemoryException на ArgumentException по какой-то непонятной для меня причине. (Вероятно, ошибка .net Framework?)

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

Итак, мой код теперь выглядит так:

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

Где:

/// <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
0 голосов
/ 14 января 2010

в случае, если вам нужно, чтобы данные читались для других операций и / или для других типов файлов (например, PSD), затем использование функции Image.FromStream не всегда является хорошей идеей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...