在C#中验证文件中的图像

时间:2008-10-16 23:33:56

标签: c# .net image file-io

我正在从文件加载图像,我想知道如何在从文件中完全读取图像之前验证图像。

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

14 个答案:

答案 0 :(得分:63)

这是我的图像检查。我不能依赖文件扩展名,必须自己检查格式。 我正在从字节数组加载WPF中的BitmapImages,并且不知道前面的格式。 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;
}

答案 1 :(得分:32)

使用Windows窗体:

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 ,因为系统资源不足,而不是因为图像损坏导致结果不正确,并且如果您在此步骤后删除图像,你可能会删除好的。

答案 2 :(得分:23)

JPEG没有正式的标题定义,但它们确实有少量的元数据可供您使用。

  • 偏移0(两个字节):JPEG SOI标记(FFD8十六进制)
  • 偏移2(两个字节):图像宽度(以像素为单位)
  • 偏移4(两个字节):图像高度(以像素为单位)
  • 偏移6(字节):组件数量(1 =灰度,3 = RGB)

之后还有其他一些事情,但这些并不重要。

您可以使用二进制流打开文件,并读取此初始数据,并确保OffSet 0为0,OffSet 6为1,2或3。

这至少可以让你更精确。

或者你可以捕获异常并继续前进,但我认为你想要一个挑战:)

答案 3 :(得分:18)

好吧,我继续编写了一组函数来解决问题。它首先检查标头,然后尝试在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;
}

答案 4 :(得分:12)

你可以通过嗅探标题进行粗略的输入。

这意味着您实现的每种文件格式都需要具有可识别的标题...

JPEG:前4个字节是FF D8 FF E0(实际上只有前两个字节可用于非jfif jpeg,更多信息here)。

GIF:前6个字节是“GIF87a”或“GIF89a”(更多信息here

PNG:前8个字​​节是:89 50 4E 47 0D 0A 1A 0A(更多信息here

TIFF:前4个字节是:II42或MM42(更多信息here

等...您可以找到您关心的任何图形格式的标题/格式信息,并根据需要添加到它处理的内容中。这不会做的,是告诉你文件是否是该类型的有效版本,但它会给你一个关于“图像不是图像?”的提示。它仍然可能是一个损坏或不完整的图像,因此在打开时会崩溃,所以仍然需要尝试捕捉.FromFile调用。

答案 5 :(得分:6)

这应该可以解决问题 - 您不必从标题中读取原始字节:

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

当然,您也应该捕获OutOfMemoryException,如果文件根本不是图像,这将保存您。

并且,ImageFormat为GDI +支持的所有其他主要图像类型预设了项目。

注意,您必须在ImageFormat对象上使用.Equals()而不是==(它不是枚举),因为运算符==没有重载以调用Equals方法。

答案 6 :(得分:3)

支持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();
        }
    }
}

答案 7 :(得分:1)

我会创建一个方法,如:

Image openImage(string filename);

我处理异常。如果返回的值为Null,则文件名/类型无效。

答案 8 :(得分:1)

我接受了Semicolon的回答并转换为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

答案 9 :(得分:0)

您可以读取Stream的前几个字节,并将它们与JPEG的魔术头字节进行比较。

答案 10 :(得分:0)

如果你需要为其他操作和/或其他文件类型(例如PSD)读取数据,稍后使用Image.FromStream函数则不一定是一个很好的想法。

答案 11 :(得分:0)

注意到上述所有功能的几个问题。 首先 - Image.FromFile打开给定的图像,然后将导致打开文件错误谁想要打开给定的图像文件由于任何原因。甚至应用程序本身 - 所以我已经使用Image.FromStream切换。

将api - 异常类型更改从OutOfMemoryException切换到ArgumentException之后,我发现某些原因尚不清楚。 (可能是.net框架错误?)

此外,如果.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

答案 12 :(得分:0)

2019在这里,dontnet核心3.1。 我得到Alex的答案,并将其具体化

public static bool IsImage(this byte[] fileBytes)
{
    var headers = new List<byte[]>
    {
        Encoding.ASCII.GetBytes("BM"),      // BMP
        Encoding.ASCII.GetBytes("GIF"),     // 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
    };

    return headers.Any(x => x.SequenceEqual(fileBytes.Take(x.Length)));
}

答案 13 :(得分:0)

这是我使用多次验证的方法。

public class ImageValidator
{
    private readonly Dictionary<string,byte[]> _validBytes = new Dictionary<string, byte[]>() {
        { ".bmp", new byte[] { 66, 77 } },
        { ".gif", new byte[] { 71, 73, 70, 56 } },
        { ".ico", new byte[] { 0, 0, 1, 0 } },
        { ".jpg", new byte[] { 255, 216, 255 } },
        { ".png", new byte[] { 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82 } },
        { ".tiff", new byte[] { 73, 73, 42, 0 } },
    };

    /// <summary>
    /// image formats to validate using Guids from ImageFormat.
    /// </summary>
    private readonly Dictionary<Guid, string> _validGuids = new Dictionary<Guid, string>() {
        {ImageFormat.Jpeg.Guid, ".jpg" },
        {ImageFormat.Png.Guid, ".png"},
        {ImageFormat.Bmp.Guid, ".bmp"},
        {ImageFormat.Gif.Guid, ".gif"},
        {ImageFormat.Tiff.Guid, ".tiff"},
        {ImageFormat.Icon.Guid, ".ico" }
    };

    /// <summary>
    /// Supported extensions: .jpg,.png,.bmp,.gif,.tiff,.ico
    /// </summary>
    /// <param name="allowedExtensions"></param>
    public ImageValidator(string allowedExtensions = ".jpg;.png")
    {
        var exts = allowedExtensions.Split(';');
        foreach (var pair in _validGuids.ToArray())
        {
            if (!exts.Contains(pair.Value))
            {
                _validGuids.Remove(pair.Key);
            }
        }

        foreach (var pair in _validBytes.ToArray())
        {
            if (!exts.Contains(pair.Key))
            {
                _validBytes.Remove(pair.Key);
            }
        }
    }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0063:Use simple 'using' statement", Justification = "<Pending>")]
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "<Pending>")]
    public async Task<bool> IsValidAsync(Stream imageStream, string filePath)
    {
        if(imageStream == null || imageStream.Length == 0)
        {
            return false;
        }

        //First validate using file extension
        string ext = Path.GetExtension(filePath).ToLower();
        if(!_validGuids.ContainsValue(ext))
        {
            return false;
        }

        //Check mimetype by content
        if(!await IsImageBySigAsync(imageStream, ext))
        {
            return false;
        }

        try
        {
            //Validate file using Guid.
            using (var image = Image.FromStream(imageStream))
            {
                imageStream.Position = 0;
                var imgGuid = image.RawFormat.Guid;
                if (!_validGuids.ContainsKey(imgGuid))
                {
                    return false;
                }

                var validExtension = _validGuids[imgGuid];
                if (validExtension != ext)
                {
                    return false;
                }
            }
        }
        catch (OutOfMemoryException)
        {
            return false;
        }

        return true;
    }

    /// <summary>
    /// Validate the mimetype using byte and file extension.
    /// </summary>
    /// <param name="imageStream"></param>
    /// <param name="extension"></param>
    /// <returns></returns>
    private async Task<bool> IsImageBySigAsync(Stream imageStream, string extension)
    {
        var length = _validBytes.Max(x => x.Value.Length);
        byte[] imgByte = new byte[length];
        await imageStream.ReadAsync(imgByte, 0, length);
        imageStream.Position = 0;

        if (_validBytes.ContainsKey(extension))
        {
            var validImgByte = _validBytes[extension];
            if (imgByte.Take(validImgByte.Length).SequenceEqual(validImgByte))
            {
                return true;
            }
        }

        return false;
    }
}