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

时间:2012-02-20 00:23:21

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

我不想依赖文件扩展名。我不在乎知道它是什么类型的图像(.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-catches执行正常程序是一种不好的做法。
  • Image.FromFile()将整个图像文件(如果是图像文件)加载到内存中。这是浪费我假设,因为我只需要文件类型,并且不需要在我的代码中进行任何进一步的图像处理。
  • 我不喜欢捕捉OutOfMemoryException,因为如果存在真正的内存不足问题并且我的程序吞下它并继续进行该怎么办?

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

修改:自从收到答案后,这些是三个解决方案我现在知道了:

  1. 通过Image.FromFile()和try-catch将整个图像加载到内存中。
    • 优点:对图像文件内容进行更深入的检查;涵盖了许多图像类型。
    • 缺点:最慢;从try-catch开始并将完整的映像文件加载到内存中;抓住真实的潜在危险' OutOfMemoryException异常。
  2. 检查图像文件的标题字节。
    • 优点:快速,低内存使用率。
    • 缺点:可能很脆弱;需要针对每种文件类型进行编程。
  3. 检查文件扩展名。
    • 职业选手:最快;最简单的。
    • 缺点:在所有情况下都不起作用;最容易出错的。
  4. (我没有看到明确的"胜利者"因为我可以想象每个人都适合的情况。对于我的应用程序的目的,文件类型检查很少发生,以至于方法1的性能问题不是问题。)

6 个答案:

答案 0 :(得分:10)

如果您只支持少数流行的图像格式,那么您只需读取文件的前几个字节即可根据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 )。

答案 1 :(得分:8)

  1. 如果你经常抛出异常,你只会注意到异常的性能损失。因此,除非您的程序希望看到许多无效图像(每秒数百个),否则您不应该注意到异常处理的开销。
  2. 这是判断图像是完整图像还是损坏的唯一方法。你可以像其他人推荐的那样检查标题,但是只检查开头的几个字节是否正确,其他任何东西都可能是垃圾。这是否足够好取决于您的应用程序的要求。只是阅读标题可能足以满足您的使用案例。
  3. 是的,BCL团队的设计相当糟糕。如果要加载许多大图像,那么很可能会在大对象堆中遇到真正的OOM情况。据我所知,没有办法区分这两个例外。

答案 2 :(得分:2)

我可以理解你的顾虑,但是如果你看一下Image.FromFile方法的来源,它只是GDI +调用的包装器,所以很遗憾你什么都不能做,因为我可以看到bizzare选择的异常(OutOfMemoryException) )是在GDI +中完成的

因此,您似乎无法使用当前代码或检查文件头,但这并不能保证该文件确实是一个有效的图像。

也许您应该首先考虑是否真的需要isImageFile方法?在扩展上检测图像文件,它会快得多,如果从文件加载失败,它将引发异常,这样你就可以在真正需要加载图像时处理它。

答案 3 :(得分:2)

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

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

答案 4 :(得分:1)

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

答案 5 :(得分: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;
}