如何用C#测试一个文件是否为jpeg格式?

35

使用C#,我该如何测试一个文件是否为JPEG格式?我需要检查文件的扩展名为.jpg吗?

谢谢。

16个回答

107

有几个选项:

你可以检查文件扩展名:

static bool HasJpegExtension(string filename)
{
    // add other possible extensions here
    return Path.GetExtension(filename).Equals(".jpg", StringComparison.InvariantCultureIgnoreCase)
        || Path.GetExtension(filename).Equals(".jpeg", StringComparison.InvariantCultureIgnoreCase);
}

或者检查文件头部的正确魔数

static bool HasJpegHeader(string filename)
{
    using (BinaryReader br = new BinaryReader(File.Open(filename, FileMode.Open, FileAccess.Read)))
    {
        UInt16 soi = br.ReadUInt16();  // Start of Image (SOI) marker (FFD8)
        UInt16 marker = br.ReadUInt16(); // JFIF marker (FFE0) or EXIF marker(FFE1)

        return soi == 0xd8ff && (marker & 0xe0ff) == 0xe0ff;
    }
}

另一个选项是加载图像并检查正确的类型。然而,这种方法不太高效(除非您无论如何都要加载图像),但可能会给您最可靠的结果(请注意加载和解压缩的额外成本以及可能的异常处理):

static bool IsJpegImage(string filename)
{
    try
    {
        using (System.Drawing.Image img = System.Drawing.Image.FromFile(filename)) 
        {           
            // Two image formats can be compared using the Equals method
            // See http://msdn.microsoft.com/en-us/library/system.drawing.imaging.imageformat.aspx
            //
            return img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Jpeg);
        }
    }
    catch (OutOfMemoryException)
    {
        // Image.FromFile throws an OutOfMemoryException 
        // if the file does not have a valid image format or
        // GDI+ does not support the pixel format of the file.
        //
        return false;
    }
}

1
加1分是因为你更加勤奋了。有几个想法:我会将其重命名为HasJpegExtension和ContainsJpegHeader。此外,如果在检查扩展名/头部和尝试加载文件之间文件被删除/移动等情况下,您是否希望捕获其他异常?或者直接将所有异常上升到上层是否更好? - Erich Mirabal
当然,命名可以改进,并应根据实际用例进行调整。在这个简单的例子中,我只是选择添加特定方式作为后缀。最终,我可能会使用IsJpeg或您的一些建议。由于这是一个简单的例子,我不想用异常处理来过度负荷它,但通常我会在堆栈的更高层处理与文件IO和访问权限相关的异常。 - Dirk Vollmar
2
在上面的HasJpegHeader函数中,我同意FF D8,但它并不总是FF E0。有时它是FF E1。因此,上面的函数对于一个正确的JPG文件导致了FALSE。 - Ruturaaj
1
我最初使用了HasJpegHeader方法。然而,在下面@Orwellophile的答案中可以找到更健壮的变体检查(EXIF文件有不同的标记)。 - Jordan

27

哎呀,这么多代码示例都是错的,错的,错的。

EXIF 文件有一个 0xff*e1* 的标记,JFIF 文件有一个 0xff*e0* 的标记。因此,所有依赖于 0xffe0 来检测 JPEG 文件的代码都将忽略所有 EXIF 文件。

这里有一个版本,可以同时检测两种类型,并且可以轻松地修改为仅返回 JFIF 或仅返回 EXIF(例如,在尝试恢复您的 iPhone 照片时非常有用)。

    public static bool HasJpegHeader(string filename)
    {
        try
        {
            // 0000000: ffd8 ffe0 0010 4a46 4946 0001 0101 0048  ......JFIF.....H
            // 0000000: ffd8 ffe1 14f8 4578 6966 0000 4d4d 002a  ......Exif..MM.*    
            using (BinaryReader br = new BinaryReader(File.Open(filename, FileMode.Open, FileAccess.ReadWrite)))
            {
                UInt16 soi = br.ReadUInt16();  // Start of Image (SOI) marker (FFD8)
                UInt16 marker = br.ReadUInt16(); // JFIF marker (FFE0) EXIF marker (FFE1)
                UInt16 markerSize = br.ReadUInt16(); // size of marker data (incl. marker)
                UInt32 four = br.ReadUInt32(); // JFIF 0x4649464a or Exif  0x66697845

                Boolean isJpeg = soi == 0xd8ff && (marker & 0xe0ff) == 0xe0ff;
                Boolean isExif = isJpeg && four == 0x66697845;
                Boolean isJfif = isJpeg && four == 0x4649464a;

                if (isJpeg) 
                {
                    if (isExif)
                        Console.WriteLine("EXIF: {0}", filename);
                    else if (isJfif)
                        Console.WriteLine("JFIF: {0}", filename);
                    else
                        Console.WriteLine("JPEG: {0}", filename);
                }

                return isJpeg;
                return isJfif;
                return isExif;
            }
        }
        catch
        {
            return false;
        }
    }

维基百科显示JPEG/JFIF的十六进制为4A 46 49 46,但您正在检查0x4649464a。EXIF是45 78 69 66,但您正在检查0x66697845。我假设您的代码是正确的,但为什么它们是反过来的呢? - vaindil
3
我原来想要戏弄你,但后来发现你实际上去了维基百科并进行了自己的研究,而且注意到数字不仅不同,而且实际上是相反的。所以我会回答你的问题。答案是字节序。如果你不想读另一个维基百科文章,简短版本如下:英特尔机器在存储值时是反过来的 - Orwellophile
@Orwellophile 哇,我甚至没有考虑过字节序问题。-_- 我知道这个问题,但出于某种原因,它甚至没有在我的脑海中闪现。谢谢你,我很感激! - vaindil
如果想要扩展这个功能,以便还可以检查有效的PNG文件,该怎么办? - user1932634
1
@user1932634 然后你会发现 PNG 文件的前几个字节是什么,然后将其添加到代码中。 - Andrew Morton

27

以流的方式打开文件,并查找JPEG文件的魔数

JPEG图像文件的开头是FF D8,结尾是FF D9。JPEG/JFIF文件包含ASCII码为'JFIF'(4A 46 49 46)的以空字符结尾的字符串。JPEG/Exif文件包含ASCII码为'Exif'(45 78 69 66)也是以空字符结尾的字符串。


当然,这并不是一种可靠的检查方式,因为可以轻松创建一个满足这些条件且不是 JPEG 文件的文件。 - jarnbjo
我认为这与使用案例的规模成比例。当然,接受非JPEG格式可能存在潜在的安全/DoS问题,但我认为这是另一个问题;-) - Simon Gibbs
大多数JPEG / Exif文件还包含JFIF代码,而一些JPEG文件既不包含Exif也不包含JFIF。 - hippietrail

13

你可以尝试将文件加载到图像中,然后检查格式。

Image img = Image.FromFile(filePath);
bool isBitmap = img.RawFormat.Equals(ImageFormat.Jpeg);

或者你可以打开文件并检查头部以获取类型


1
对于每个正例,您可能会解码整个图像,而您可能不想这样做;对于每个负例,我希望您需要处理一个异常 - 这会让可怜的家伙在按F5键进行代码调试时感到疲惫。我猜这取决于情况,但还有其他答案没有这些问题。 - Simon Gibbs
比较实际上失败了。你需要调用 ImageFormat.Equals 而不是使用 == 运算符。 - Dirk Vollmar

3

您可以找到关于jpeg文件格式的文档,特别是头部信息。然后尝试从文件中读取此信息,并将其与预期的jpeg头字节进行比较。


3

1

这将循环遍历当前目录中的每个文件,并输出任何找到的具有JPG或JPEG扩展名的Jpeg图像。

      foreach (FileInfo f in new DirectoryInfo(".").GetFiles())
        {
            if (f.Extension.ToUpperInvariant() == ".JPG"
                || f.Extension.ToUpperInvariant() == ".JPEG")
            {
                Image image = Image.FromFile(f.FullName);

                if (image.RawFormat == ImageFormat.Jpeg)
                {
                    Console.WriteLine(f.FullName + " is a Jpeg image");
                }
            }
        }

1

0

只需获取文件的媒体类型并进行验证:

private bool isJpeg()
        {
string p = currFile.Headers.ContentType.MediaType;
            return p.ToLower().Equals("image/jpeg") || p.ToLower().Equals("image/pjpeg") || p.ToLower().Equals("image/png");
        }

0

你的回答是为什么只给链接作为答案不可取的典型例子。该页面现在已经不存在,答案现在毫无用处。如果你能改进你的回答,那就太好了,否则应该将其删除或标记为删除。 - Mrchief
在使用他人内容并提供足够的来源/访问原始材料之间有一条微妙的界限。我选择了标注出处。问题在于所引用的用户没有提供到新位置的重定向甚至没有给出"Gone"。 - Program.X
是的,我在某种程度上同意你的观点。注明来源是必需的,你已经通过包含链接来做到了这一点。不幸的是,SO并不仅仅是一个为别人提供信用的地方——它的目的是提供答案,而这正是这里缺少的。接受的答案和@simongibbs的答案是如何同时做到这一点的一个很好的例子,SO中还有很多其他可以作为参考的答案。就像你所说的那样,因为404是残酷的现实,你不能指望每个人都返回302,包含一个gist是有帮助且非常必要的。 - Mrchief

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