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

kyxcudwk  于 2023-01-22  发布在  .NET
关注(0)|答案(7)|浏览(263)

我不想依赖于文件扩展名。我不关心它是什么类型的图像(.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()将整个图像文件(如果它是图像文件)加载到内存中,我认为这是浪费,因为我只需要文件类型,而不需要在代码的这一点上做任何进一步的图像操作。
  • 我不喜欢捕获OutOfMemoryException s,因为如果真实的的出现内存不足的问题,而我的程序吞下它并继续运行,那该怎么办?
    **是否有更好的方法来实现这一点?**或者,我上面列出的任何/所有顾虑是否都是没有根据的?
    编辑:自从在这里收到答案后,我现在知道三种解决方案
  • 通过Image.FromFile()和try-catch将整个图像加载到内存中。
    • 优点 *:对图像文件内容进行更深入的检查;涵盖许多图像类型。
    • 缺点 *:最慢;来自try-catch和将完整图像文件加载到内存中的开销;捕获“真实的的”OutOfMemoryException的潜在危险。
  • 检查图像文件的标头字节。
    • 优点 *:速度快,内存使用率低。
    • 缺点 *:可能易碎;需要为每种文件类型编程。
  • 检查文件扩展名。
    • 优点 *:最快;最简单。
    • 缺点 *:并非在所有情况下都有效;最容易出错。

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

p8ekf7hl

p8ekf7hl1#

如果您只支持少数几种流行的图像格式,那么您可以简单地读取文件的前几个字节,根据Magic Number来确定类型
所提供链接中的示例:

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

w8f9ii692#

1.只有当你不断抛出异常时,你才会注意到异常对性能的影响,所以除非你的程序期望看到很多无效图像(每秒数百个),否则你不应该注意异常处理的开销。
1.这确实是判断映像是完整映像还是损坏映像的唯一方法。您可以按照其他人的建议检查标头,但这仅检查开头几个字节是否正确,其他任何内容都可能是垃圾。这是否足够好取决于您的应用程序的要求。仅阅读标头可能对您的用例足够好。
1.是的,这是BCL团队的一个相当糟糕的设计。如果你加载很多大的图像,你很可能会在大对象堆中遇到一个真实的的OOM情况。据我所知,没有办法区分这两个例外。

7ivaypg9

7ivaypg93#

我可以理解您的顾虑,但是如果您查看Image.FromFile方法的源代码,它只是GDI+调用的 Package 器,因此遗憾的是您不能什么也不做,因为正如我所看到的,异常的bizzare选择(OutOfMemoryException)是在GDI+中完成的
所以看起来你被当前的代码卡住了,或者检查文件头,但是这并不能保证文件真的是一个有效的图像。
也许你应该首先考虑一下你真的需要isImageFile方法吗?在扩展名上检测图像文件,这样会快得多,如果从文件加载失败,它会引发一个异常,这样你就可以在真正需要加载图像时处理它。

ybzsozfc

ybzsozfc4#

采用检查文件头的方法,我编写了以下实现:

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,
    }

我把它和方法沿着放到一个实用程序/helper类中:GetFileImageTypeFromFullLoad()GetFileImageTypeFromExtension()。前者使用我上面提到的Image.FromFile方法,后者只是检查文件扩展名。我计划根据情况的需要使用这三种方法。

jvidinwx

jvidinwx5#

首先使用System.IO.Path.GetExtension()方法来检查扩展是否是图像类型。然后如果你想完成,你可以检查文件中的头。

kse8i1jr

kse8i1jr6#

下面是一个使用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;
}
5hcedyr0

5hcedyr07#

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

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

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

相关问题