如何在.NET中确定文件是否为图像文件?

19

我不想依赖于文件扩展名,也不关心图片的类型(如.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.aspx,如果文件不是有效的图像格式,则Image.FromFile()会抛出OutOfMemoryException。使用上述方法可以得到恰好我想要的结果,但是出于以下原因,我宁愿不使用它:
  • 我认为在正常程序执行中使用try-catch是一种性能不佳的坏习惯。
  • Image.FromFile()将整个图像文件(如果是图像文件)加载到内存中。这是浪费的,因为我只需要文件类型,并且在代码的这个点上不需要进行任何进一步的图像操作。
  • 我不喜欢捕获OutOfMemoryException,因为如果有真正的内存不足问题,我的程序会吞噬它并继续运行,那该怎么办?

是否有更好的方法来解决这个问题? 或者,上述所有/部分问题是否无关紧要?

编辑:自从在这里接收到答案以来,这些是我现在了解到的三种解决方案:

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

(我没有看到明显的“赢家”,因为我可以想象出每种方法都适用的情况。对于我的应用程序目的,文件类型检查不太频繁,因此方法1的性能问题不是问题。)


参见:https://dev59.com/vHRB5IYBdhLWcg3wSVcI - Daryl
请参见#2:https://dev59.com/dnVC5IYBdhLWcg3wsTbv - Daryl
请参见#3:https://dev59.com/PnVD5IYBdhLWcg3wL4qM - Daryl
7个回答

11
如果你只需要支持少数流行的图像格式,那么你可以简单地读取文件的前几个字节来确定类型,基于魔数
来自提供的链接的示例:
- GIF图像文件具有ASCII代码“GIF89a”(47 49 46 38 39 61)或“GIF87a”(47 49 46 38 37 61)。 - JPEG图像文件以FF D8开头,并以FF D9结尾。JPEG/JFIF文件包含ASCII代码“JFIF”(4A 46 49 46)作为空终止字符串。 - PNG图像文件以8字节签名开头,用于标识文件为PNG文件并允许检测常见的文件传输问题:\211 P N G \r \n \032 \n(89 50 4E 47 0D 0A 1A 0A)。

1
谢谢提供这个信息,我之前不知道可以这样做。我已经写了一个实现并将其发布在维基上。 - Daryl

8
  1. 只有当您不断抛出异常时,才会注意到异常对性能的影响。因此,除非您的程序预计每秒会看到许多无效图像(每秒数百个),否则您不应该注意到异常处理的开销。
  2. 这确实是唯一的方法来确定图像是否完整或损坏。您可以像其他人建议的那样检查标头,但这只检查前几个字节是否正确,其他任何东西都可能是垃圾。是否足够好取决于您的应用程序的要求。仅读取标头可能已经足够适合您的用例。
  3. 是的,这确实是BCL团队设计上的缺陷。如果您正在加载许多大型图像,则很可能会在大对象堆中遇到真正的OOM情况。据我所知,没有办法区分这两种异常。

谢谢提供信息。我决定在我的情况下,性能损失并不显著。 - Daryl

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:
        //https://dev59.com/gWox5IYBdhLWcg3wNRtj
        //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方法,而后者仅检查文件扩展名。根据情况的要求,我打算同时使用这三种方法。

这是我找到的另一个。它基本上做同样的事情,但体积更小一些: - Justin
1
@Panuvin,你忘记包含链接了吗? - Daryl

3
我可以理解你的担忧,但如果你查看 Image.FromFile 方法的源代码,你会发现它只是 GDI+ 调用的封装器。因此很遗憾,你无法做任何事情,因为我可以看到,GDI+ 选择使用异常(OutOfMemoryException)是比较奇怪的。
所以看起来你只能 stuck with 当前的代码,或者检查文件头,但这并不能保证文件确实是有效的图像文件。
也许你应该考虑一下是否真的需要 isImageFile 方法?通过检测文件扩展名来检测图像文件,速度会更快,并且如果从文件加载失败,它将引发异常,这样你就可以在真正需要加载图像时处理它。

1
这是一个与 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;
}

1

首先使用System.IO.Path.GetExtension()方法检查扩展名是否为图像类型,然后如果你想要更加准确,可以检查文件头。


2
如果没有扩展名怎么办? - user5447154

0

要检查文件是否为正确的图像文件,您可以使用ImageSharp进行加载:

using var image = await SixLabors.ImageSharp.Image.LoadAsync(imageStream);

如果它抛出异常,那么它不是一个正确的图像文件。请注意,ImageSharp仅支持某些图像格式的子集。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接