54

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

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

当 image.jpg 不是真正的 jpg 时,就会出现问题。例如,如果我创建一个空文本文件并将其重命名为 image.jpg,则在加载 image.jpg 时会抛出 OutOfMemory Exception。

我正在寻找一个函数来验证给定图像的流或文件路径的图像。

示例函数原型

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

14 回答 14

84

这是我的图像检查。我不能依赖文件扩展名,必须自己检查格式。我正在从字节数组中加载 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;
}
于 2012-02-25T16:59:19.643 回答
33

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

于 2008-10-16T23:43:28.047 回答
23

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

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

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

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

这至少会给你带来更高的精确度。

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

于 2008-10-16T23:42:38.983 回答
20

好吧,我继续编写了一组函数来解决这个问题。它首先检查标题,然后尝试在 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;
}
于 2008-10-17T05:23:23.757 回答
14

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

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

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 调用。

于 2008-10-16T23:46:55.637 回答
6

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

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

当然,您也应该捕获 OutOfMemoryException,如果文件根本不是图像,这将节省您的时间。

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

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

于 2010-06-29T19:53:24.773 回答
5

2019 年,dotnet core 3.1。我接受亚历克斯的回答并稍微实现它

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

用法 :

public async Task UploadImage(Stream file)
{
    using (MemoryStream ms = new MemoryStream())
    {
        await file.CopyToAsync(ms);

        byte[] bytes = ms.ToArray();

        if (!bytes.IsImage())
            throw new ArgumentException("Not an image", nameof(file));

        // Upload your file
    }
}
于 2019-12-24T11:03:58.337 回答
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();
        }
    }
}
于 2010-03-11T13:00:34.030 回答
3

注意到上述所有功能的几个问题。首先 - 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
于 2015-12-04T11:33:30.973 回答
1

我接受了分号的答案并转换为 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
于 2012-02-09T17:37:15.523 回答
0

我会创建一个类似的方法:

Image openImage(string filename);

我在其中处理异常。如果返回值为 Null,则说明文件名/类型无效。

于 2008-10-16T23:38:52.813 回答
0

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

于 2008-10-16T23:43:24.177 回答
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;
    }
}
于 2020-02-08T23:50:52.863 回答
-1

如果您稍后需要为其他操作和/或其他文件类型(例如 PSD)读取数据,那么使用该Image.FromStream功能不一定是一个好主意。

于 2010-01-14T11:17:48.567 回答