【问题标题】:How can I determine if a file is an image file in .NET?如何确定文件是否为 .NET 中的图像文件?
【发布时间】:2012-03-10 10:00:18
【问题描述】:

我不想依赖文件扩展名。我不关心它是什么类型的图像(.jpg、.png 等),我只是想知道文件是否是图像。如果可能,我宁愿不使用任何非 .NET dll。

我知道如何做到这一点的最佳方法如下:

bool isImageFile;
try
{
    Image.FromFile(imageFile).Dispose();
    isImageFile = true;
}
catch (OutOfMemoryException)
{
    isImageFile = false;
}

如此处所述:http://msdn.microsoft.com/en-us/library/stf701f5.aspxImage.FromFile() 如果文件不是有效的图像格式,则抛出 OutOfMemoryException。使用上面的方法给了我正是我想要的结果,但我不想使用它,原因如下:

  • 我认为,出于性能原因,将 try-catch 用于正常程序执行是一种不好的做法。
  • Image.FromFile() 将整个图像文件(如果是图像文件)加载到内存中。我认为这很浪费,因为我只需要文件类型,此时不需要在我的代码中进行任何进一步的图像处理。
  • 我不喜欢捕捉OutOfMemoryExceptions,因为如果出现真正的内存不足问题并且我的程序吞下了它并继续运行怎么办?

有没有更好的方法来做到这一点?或者,我上面列出的任何/所有问题都是没有根据的吗?

编辑:自从在这里收到答案后,以下是我现在知道的三种解决方案

  1. 通过Image.FromFile() 和try-catch 将整个图像加载到内存中。
    • 优点:对图像文件内容进行更深入的检查;涵盖许多图像类型。
    • 缺点:最慢; try-catch 和将完整图像文件加载到内存中的开销;捕获“真正的”OutOfMemoryException 的潜在危险。
  2. 检查图像文件的标头字节。
    • 优点:快速、低内存使用。
    • 缺点:可能很脆弱;需要针对每种文件类型进行编程。
  3. 检查文件扩展名。
    • 优点:最快;最简单。
    • 缺点:并非在所有情况下都有效;最容易出错。

(我没有看到明确的“赢家”,因为我可以想象每个人都适合的情况。出于我的应用程序的目的,文件类型检查很少发生,以至于方法 1 的性能问题不是问题。)

【问题讨论】:

标签: .net image file-format file-type


【解决方案1】:

如果您只支持少数几种流行的图像格式,那么您只需读取文件的前几个字节即可根据 Magic Number 确定类型

来自所提供链接的示例:

  • GIF 图像文件的 ASCII 码表示“GIF89a”(47 49 46 38 39 61)或“GIF87a”(47 49 46 38 37 61)
  • JPEG 图像文件以 FF D8 开头,以 FF D9 结尾。 JPEG/JFIF 文件包含“JFIF”(4A 46 49 46) 的 ASCII 代码作为空终止字符串。
  • PNG 图像文件以 8 字节签名开始,该签名将文件标识为 PNG 文件并允许检测常见的文件传输问题:\211 PNG \r \n \032 \n (89 50 4E 47 0D 0A 1A 0A )。

【讨论】:

  • 感谢您的信息;我不知道这可以做到。我编写了一个实现并将其作为 wiki 发布在这里。
【解决方案2】:
  1. 只有在不断抛出异常时,才会注意到异常对性能的影响。因此,除非您的程序预计会看到许多无效图像(每秒数百个),否则您不应注意到异常处理的开销。
  2. 这确实是判断图​​像是完整图像还是损坏的唯一方法。您可以按照其他人的建议检查标头,但这仅检查开头的几个字节是否正确,其他任何内容都可能是垃圾。这是否足够好取决于您的应用程序的要求。只需阅读标题可能就足以满足您的用例。
  3. 是的,这对 BCL 团队来说是相当糟糕的设计。如果您正在加载许多大图像,那么您很可能会在大对象堆中遇到真正的 OOM 情况。据我所知,没有办法区分这两种例外情况。

【讨论】:

  • 感谢您的信息。根据我的情况,我决定性能影响并不显着。
【解决方案3】:

我可以理解您的担忧,但是如果您查看 Image.FromFile 方法的来源,它只是 GDI+ 调用的包装器,所以遗憾的是您无能为力,因为我可以看到异常的奇怪选择 (OutOfMemoryException ) 在 GDI+ 中完成

因此,您似乎被当前代码卡住了,或者检查了文件头,但这并不能保证该文件确实是一个有效的图像。

也许你应该首先考虑你真的需要 isImageFile 方法吗?检测扩展的图像文件,它会更快,如果从文件加载失败会引发异常,以便您在真​​正需要加载图像时处理它。

【讨论】:

    【解决方案4】:

    走检查文件头的路线,我写了这个实现:

    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 方法,后者只是检查文件扩展名。我计划根据情况的要求使用所有这三个。

    【讨论】:

    • 这是我遇到的另一个。它的作用基本相同,但要小一些:
    • @Panuvin,您忘记添加链接了吗?
    【解决方案5】:

    首先使用 System.IO.Path.GetExtension() 方法检查 Extension 是否为图像类型。然后如果你想通过你可以检查文件中的标题。

    【讨论】:

    • 如果没有扩展怎么办?
    【解决方案6】:

    这里有一个使用 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;
    }
    

    【讨论】:

      猜你喜欢
      • 2010-10-14
      • 1970-01-01
      • 2010-10-30
      • 2015-12-02
      • 2021-12-17
      • 2014-12-26
      • 1970-01-01
      • 1970-01-01
      • 2019-09-26
      相关资源
      最近更新 更多