确定文件是不是为图像

Posted

技术标签:

【中文标题】确定文件是不是为图像【英文标题】:determine if file is an image确定文件是否为图像 【发布时间】:2010-10-14 19:23:12 【问题描述】:

我正在遍历一个目录并复制所有文件。现在我正在做string.EndsWith 检查".jpg"".png" 等。 .

有没有更优雅的方法来确定文件是否是图像(任何图像类型)而无需像上面那样的 hacky 检查?

【问题讨论】:

扩展不够好有什么原因吗? 另见:***.com/questions/9354747/… @peterchen 是的,也许文件是图像但不知何故重命名为其他东西 【参考方案1】:

这是一个棘手的问题。如果文件不是图像,则会引发异常。从中我们可以检查文件是否为图像。

        using (Stream stream = File.OpenRead(file))
           
               try
               
                   using (Image sourceImage = Image.FromStream(stream, false, false))
                   

                   
               
               catch (Exception x)
               
                   if (x.Message.Contains("not valid"))
                   
                     Console.Write("This is not a Image.");
                   

               
           

【讨论】:

【参考方案2】:

这就是我使用的——它只是对@dylmcc 答案的调整,使其更具可读性。

public static bool IsRecognisedImageFile(string fileName)

    string targetExtension = System.IO.Path.GetExtension(fileName);
    if (String.IsNullOrEmpty(targetExtension))
    
        return false;
    

    var recognisedImageExtensions = System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders().SelectMany(codec => codec.FilenameExtension.ToLowerInvariant().Split(';'));

    targetExtension = "*" + targetExtension.ToLowerInvariant();
    return recognisedImageExtensions.Contains(targetExtension);

【讨论】:

-1。问题是如何测试文件而不是文件名。您可以将文本文件保存为 .png 或确实损坏的图像文件,这些文件不会作为图像打开。 这是解释问题的一种方式 - 在信任扩展作为现实的代理与实际尝试使用手头的任何工具处理图像之间当然存在权衡。【参考方案3】:

我的简单代码

public static List<string> GetAllPhotosExtensions()
    
        var list = new List<string>();
        list.Add(".jpg");
        list.Add(".png");
        list.Add(".bmp");
        list.Add(".gif");
        list.Add(".jpeg");
        list.Add(".tiff");
        return list;
    

检查是否有图片文件

public static bool IsPhoto(string fileName)
    
        var list = FileListExtensions.GetAllPhotosExtensions();
        var filename= fileName.ToLower();
        bool isThere = false;
        foreach(var item in list)
        
            if (filename.EndsWith(item))
            
                isThere = true;
                break;
            
        
        return isThere;     
    

【讨论】:

文件类型由签名决定。不是按文件扩展名。【参考方案4】:

如果您想在从文件完全读取之前快速验证图像文件,除了比较文件扩展名之外,您还可以检查其标题以查找文件签名(以下代码 IsValidImageFile() 检查 BMP、GIF87a、GIF89a、PNG、TIFF、JPEG)

    /// <summary>
    /// Reads the header of different image formats
    /// </summary>
    /// <param name="file">Image file</param>
    /// <returns>true if valid file signature (magic number/header marker) is found</returns>
    private bool IsValidImageFile(string file)
    
        byte[] buffer = new byte[8];
        byte[] bufferEnd = new byte[2];

        var bmp = new byte[]  0x42, 0x4D ;               // BMP "BM"
        var gif87a = new byte[]  0x47, 0x49, 0x46, 0x38, 0x37, 0x61 ;     // "GIF87a"
        var gif89a = new byte[]  0x47, 0x49, 0x46, 0x38, 0x39, 0x61 ;     // "GIF89a"
        var png = new byte[]  0x89, 0x50, 0x4e, 0x47, 0x0D, 0x0A, 0x1A, 0x0A ;   // PNG "\x89PNG\x0D\0xA\0x1A\0x0A"
        var tiffI = new byte[]  0x49, 0x49, 0x2A, 0x00 ; // TIFF II "II\x2A\x00"
        var tiffM = new byte[]  0x4D, 0x4D, 0x00, 0x2A ; // TIFF MM "MM\x00\x2A"
        var jpeg = new byte[]  0xFF, 0xD8, 0xFF ;        // JPEG JFIF (SOI "\xFF\xD8" and half next marker xFF)
        var jpegEnd = new byte[]  0xFF, 0xD9 ;           // JPEG EOI "\xFF\xD9"

        try
        
            using (System.IO.FileStream fs = new System.IO.FileStream(file, System.IO.FileMode.Open, System.IO.FileAccess.Read))
            
                if (fs.Length > buffer.Length)
                
                    fs.Read(buffer, 0, buffer.Length);
                    fs.Position = (int)fs.Length - bufferEnd.Length;
                    fs.Read(bufferEnd, 0, bufferEnd.Length);
                

                fs.Close();
            

            if (this.ByteArrayStartsWith(buffer, bmp) ||
                this.ByteArrayStartsWith(buffer, gif87a) ||
                this.ByteArrayStartsWith(buffer, gif89a) ||
                this.ByteArrayStartsWith(buffer, png) ||
                this.ByteArrayStartsWith(buffer, tiffI) ||
                this.ByteArrayStartsWith(buffer, tiffM))
            
                return true;
            

            if (this.ByteArrayStartsWith(buffer, jpeg))
            
                // Offset 0 (Two Bytes): JPEG SOI marker (FFD8 hex)
                // Offest 1 (Two Bytes): Application segment (FF?? normally ??=E0)
                // Trailer (Last Two Bytes): EOI marker FFD9 hex
                if (this.ByteArrayStartsWith(bufferEnd, jpegEnd))
                
                    return true;
                
            
        
        catch (Exception ex)
        
            MessageBox.Show(ex.Message, Lang.Lang.ErrorTitle + " IsValidImageFile()", MessageBoxButtons.OK, MessageBoxIcon.Error);
        

        return false;
    

    /// <summary>
    /// Returns a value indicating whether a specified subarray occurs within array
    /// </summary>
    /// <param name="a">Main array</param>
    /// <param name="b">Subarray to seek within main array</param>
    /// <returns>true if a array starts with b subarray or if b is empty; otherwise false</returns>
    private bool ByteArrayStartsWith(byte[] a, byte[] b)
    
        if (a.Length < b.Length)
        
            return false;
        

        for (int i = 0; i < b.Length; i++)
        
            if (a[i] != b[i])
            
                return false;
            
        

        return true;
    

检查标头签名可以很快,因为它不会加载整个文件或创建大型对象,特别是在处理多个文件时。但它不会检查其余数据是否格式正确。为此,可以尝试将文件加载到 Image 对象的第二步(这样可以确保文件可以由您的程序显示和处理)。

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;

【讨论】:

什么是“bufferEnd”?似乎是从文件末尾读取的?当文件不属于文件系统(需要大量跳过)时,这可能并不总是可能的,不是吗?另外,WEBP 呢?那么新的 HEIC 呢?【参考方案5】:

检查文件中的known header。 (this answer 中也提到了来自链接的信息)

PNG 文件的前八个字节始终包含以下(十进制)值:137 80 78 71 13 10 26 10

【讨论】:

注意第二个到第六个字符是“PNG\r\n” 抱歉,您的链接已失效。我遇到了 403 错误。 链接内容最初于 2008 年 7 月发布,存档于 WayBackMachine【参考方案6】:
System.Web.MimeMapping.GetMimeMapping(filename).StartsWith("image/");

MimeMapping.GetMimeMapping 产生这些结果:

file.jpg:图片/jpeg file.gif: 图像/gif file.jpeg: 图像/jpeg file.png: 图像/png file.bmp: 图像/bmp file.tiff:图像/tiff file.svg:应用程序/八位字节流

file.svg 不返回图像/MIME 类型在大多数情况下都有效,因为您可能不会像处理标量图像那样处理矢量图像。检查 MIME 类型时,请注意 SVG 确实具有标准的 MIME 类型 image/svg+xml,即使 GetMimeMapping 没有返回它。

【讨论】:

如果故意更改文件扩展名,例如从 .jpg 到 .txt,这将失败,即 System.Web.MimeMapping.GetMimeMapping(filename) 的输出将是 text/plain【参考方案7】:

我使用以下方法。它使用内置的图像解码器来检索系统识别为图像文件的扩展名列表,然后将这些扩展名与您传入的文件名的扩展名进行比较。返回一个简单的 TRUE/FALSE。

public static bool IsRecognisedImageFile(string fileName)

    string targetExtension = System.IO.Path.GetExtension(fileName);
    if (String.IsNullOrEmpty(targetExtension))
        return false;
    else
        targetExtension = "*" + targetExtension.ToLowerInvariant();

    List<string> recognisedImageExtensions = new List<string>();

    foreach (System.Drawing.Imaging.ImageCodecInfo imageCodec in System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders())
        recognisedImageExtensions.AddRange(imageCodec.FilenameExtension.ToLowerInvariant().Split(";".ToCharArray()));

    foreach (string extension in recognisedImageExtensions)
    
        if (extension.Equals(targetExtension))
        
            return true;
        
    
    return false;

【讨论】:

这可以很好地替代***.com/a/14587821/329367【参考方案8】:

这将查看文件的前几个字节并确定它是否是图像。

using System.Collections.Generic;
using System.IO;
using System.Linq;

public static class Extension

    public static bool IsImage(this Stream stream)
    
        stream.Seek(0, SeekOrigin.Begin);

        List<string> jpg = new List<string>  "FF", "D8" ;
        List<string> bmp = new List<string>  "42", "4D" ;
        List<string> gif = new List<string>  "47", "49", "46" ;
        List<string> png = new List<string>  "89", "50", "4E", "47", "0D", "0A", "1A", "0A" ;
        List<List<string>> imgTypes = new List<List<string>>  jpg, bmp, gif, png ;

        List<string> bytesIterated = new List<string>();

        for (int i = 0; i < 8; i++)
        
            string bit = stream.ReadByte().ToString("X2");
            bytesIterated.Add(bit);

            bool isImage = imgTypes.Any(img => !img.Except(bytesIterated).Any());
            if (isImage)
            
                return true;
            
        

        return false;
    


编辑

我对上述内容进行了一些更改,以便您可以根据需要添加自己的图片,还删除了开始时不必要的收藏。我还添加了一个接受string 类型的out 参数的重载,将值设置为组成流的图像类型。

public static class Extension

    static Extension()
    
        ImageTypes = new Dictionary<string, string>();
        ImageTypes.Add("FFD8","jpg");
        ImageTypes.Add("424D","bmp");
        ImageTypes.Add("474946","gif");
        ImageTypes.Add("89504E470D0A1A0A","png");
    
    
    /// <summary>
    ///     <para> Registers a hexadecimal value used for a given image type </para>
    ///     <param name="imageType"> The type of image, example: "png" </param>
    ///     <param name="uniqueHeaderAsHex"> The type of image, example: "89504E470D0A1A0A" </param>
    /// </summary>
    public static void RegisterImageHeaderSignature(string imageType, string uniqueHeaderAsHex)
    
        Regex validator = new Regex(@"^[A-F0-9]+$", RegexOptions.CultureInvariant);
    
        uniqueHeaderAsHex = uniqueHeaderAsHex.Replace(" ", "");
        
        if (string.IsNullOrWhiteSpace(imageType))         throw new ArgumentNullException("imageType");
        if (string.IsNullOrWhiteSpace(uniqueHeaderAsHex)) throw new ArgumentNullException("uniqueHeaderAsHex");
        if (uniqueHeaderAsHex.Length % 2 != 0)            throw new ArgumentException    ("Hexadecimal value is invalid");
        if (!validator.IsMatch(uniqueHeaderAsHex))        throw new ArgumentException    ("Hexadecimal value is invalid");
        
        ImageTypes.Add(uniqueHeaderAsHex, imageType);
    
    
    private static Dictionary<string, string> ImageTypes;

    public static bool IsImage(this Stream stream)
    
        string imageType;
        return stream.IsImage(out imageType);
    
    
    public static bool IsImage(this Stream stream, out string imageType)
    
        stream.Seek(0, SeekOrigin.Begin);
        StringBuilder builder = new StringBuilder();
        int largestByteHeader = ImageTypes.Max(img => img.Value.Length);
        
        for (int i = 0; i < largestByteHeader; i++)
        
            string bit = stream.ReadByte().ToString("X2");
            builder.Append(bit);
            
            string builtHex = builder.ToString();
            bool isImage = ImageTypes.Keys.Any(img => img == builtHex);
            if (isImage)
            
                imageType = ImageTypes[builder.ToString()];
                return true;
            
        
        imageType = null;
        return false;
    

【讨论】:

不实用,因为您缺少很多可能的图像类型。 一些错别字:应该是 return IsImage(stream, out imageType);而不是 return stream.IsImage(out imageType);应该是 int maximumByteHeader = ImageTypes.Max(img => img.Key.Length) 而不是 int maximumByteHeader = ImageTypes.Max(img => img.Value.Length) 实现你刚刚写的,看看它是否能编译 您的编辑在int largestByteHeader = ImageTypes.Max(img =&gt; img.Value.Length); 行有一个小错误 - 长度应除以 2,因为十六进制字符串中的每个字符仅值 4 位而不是 8,您使用此结果声明,好像它是标头中的字节数。以 PNG 为例,标头为 8 个字节,但您的代码错误地将其计算为 16 个字节。它仍然有效,因为它只是在非图像情况下扫描了比必要的更长的时间,但它使代码混乱。 另外,@an-phu 是对的,img.Value.Length 应该是 img.Key.Length(虽然你的扩展方法是正确的,但不需要更改)。【参考方案9】:

我们可以使用命名空间 System.Drawing 中的 Image 和 graphics 类;做我们的工作。如果代码正常工作,则它是图像,否则不是。那就是让 DotNet 框架为我们完成这项工作。 代码 -

public string CheckFile(file)

    string result="";
    try
    
        System.Drawing.Image imgInput = System.Drawing.Image.FromFile(file);
        System.Drawing.Graphics gInput = System.Drawing.Graphics.fromimage(imgInput);  
        Imaging.ImageFormat thisFormat = imgInput.RawFormat;   
        result="It is image";        
    
    catch(Exception ex)
    
        result="It is not image"; 
    
    return result;

【讨论】:

将此代码添加到尝试部分:imgInput.Dispose(); gInput.Dispose(); 。除非文件句柄会被打开而其他进程不能使用它。 请注意:虽然这可行,但它会占用系统资源和内存。可以在这里和那里进行一些图像检查,但不适用于人们可以上传任何大小图像的网络环境。【参考方案10】:

我不确定此解决方案的性能缺陷是什么,但您不能在 try 块中对文件执行一些图像功能,如果它不是图像,它将失败并落入 catch 块?

此策略可能不是所有情况下的最佳解决方案,但在我目前使用它的情况下,它具有一个主要优势: 您可以使用计划用于处理图像(如果是图像)的任何功能来测试功能。通过这种方式,您可以测试所有当前的图像类型,但它也可以扩展到未来的图像类型,而无需将新的图像扩展添加到您支持的图像类型列表中。

有人认为这种策略有什么缺点吗?

【讨论】:

不希望故意使用 try/catch,因为异常/可能很昂贵(开销明智)并且它有轻微的代码气味。这不是一个优雅的解决方案。请参阅 dylmcc 以获得最佳解决方案。我已经使用它并且它有效。我看不出该解决方案有任何退路..【参考方案11】:

看看this 是否有帮助。

编辑:另外, Image.FromFile(....).RawFormat 可能会有所帮助。如果文件不是图像,它可能会抛出异常。

【讨论】:

【参考方案12】:

不完全是您需要的答案。但如果是互联网,则输入MIME。

【讨论】:

【参考方案13】:

查看System.IO.Path.GetExtension

这是一个快速示例。

public static readonly List<string> ImageExtensions = new List<string>  ".JPG", ".JPE", ".BMP", ".GIF", ".PNG" ;

private void button_Click(object sender, RoutedEventArgs e)

    var folder = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
    var files = Directory.GetFiles(folder);
    foreach(var f in files)
    
        if (ImageExtensions.Contains(Path.GetExtension(f).ToUpperInvariant()))
        
            // process image
        
    

【讨论】:

他说他正在检查 string.EndsWith 是否来自我收集的内容 是的,但它仍然是字符串比较。如果我将 .jpf 文件重命名为 .txt 会怎样? 这个问题有两种可能的解释;要么'EndsWith'是hacky(在这种情况下,这个答案是OP想要的),或者'使用文件名'是hacky,在这种情况下@MitchWheat的答案是OP想要的。我更喜欢 Mitch 的,但两者都赞成。 如果您使用扩展名,请务必在进行比较之前将其转换为小写。 (例如,许多数码相机会生成 .JPG 文件。)

以上是关于确定文件是不是为图像的主要内容,如果未能解决你的问题,请参考以下文章

确定图像存储是不是针对设备进行了优化

jQuery:检查图像是不是存在

确定图像是不是相同,但大小不同

确定 CGPoint 是不是在图像区域内

R确定图像宽度和高度(以像素为单位)

检查灰度图像中的像素是不是为黑色(OpenCV)