How can I determine if a file is an image file in .NET?

生来就可爱ヽ(ⅴ<●) 提交于 2019-11-27 02:42:45

问题


I don't want to rely on the file extension. I don't care to know what type of image it is (.jpg, .png, etc.), I simply want to know if the file is an image or not. I'd prefer to not use any non-.NET dlls if possible.

The best way I know how to do this is the following:

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

As noted here: http://msdn.microsoft.com/en-us/library/stf701f5.aspx, Image.FromFile() throws an OutOfMemoryException if the file isn't a valid image format. Using the above gives me exactly the result I want, however I'd prefer not to use it for the following reasons:

  • It is my belief that using try-catches for normal program execution is a bad practice for performance reasons.
  • Image.FromFile() loads the whole image file (if it is an image file) into memory. This is wasteful I assume because I only need the file type and don't need to do any further image manipulation at this point in my code.
  • I dislike catching OutOfMemoryExceptions because what if there is a REAL out-of-memory problem and my program swallows it and keeps going?

Are there any better ways to do this? Or, are any/all of my concerns listed above unwarranted?

Edit: Since receiving the answers here, these are the three solutions I'm now aware of:

  1. Load the whole image in memory via Image.FromFile() and a try-catch.
    • Pros: Does a deeper check against the image files contents; covers many image types.
    • Cons: Slowest; overhead from try-catch and loading full image file into memory; potential danger from catching a 'real' OutOfMemoryException.
  2. Check the header bytes of the image file.
    • Pros: Quick, low memory usage.
    • Cons: potentially brittle; need to program for every file type.
  3. Check the file extension.
    • Pros: Quickest; simplest.
    • Cons: Doesn't work in all situations; most easily wrong.

(I do not see a clear "winner" since I can imagine a situation in which each one would be appropriate. For my application's purposes, the file type checking happens infrequently enough that the performance concerns of method 1 weren't an issue.)


回答1:


  1. You will only notice a performance hit from exceptions if you are constantly throwing them. So unless your program expects to see many invalid images (hundreds per second) you should not notice the overhead of exception handling.
  2. This is really the only way to tell if the image is a full image or corrupt. You can check the headers as the other people recommend, but that only checks to see if the beginning few bytes are correct, anything else could be garbage. Whether this is good enough or not depends on the requirements of your application. Just reading the header might be good enough for your use case.
  3. Yes, this is rather poor design on the BCL team's part. If you are loading many large images you very well could be hitting a real OOM situation in the large object heap. As far as I know, there is no way to differentiate the two exceptions.



回答2:


If you will only be supporting a handful of the popular image formats, then you can simply read the first few bytes of the file to determine the type based on the Magic Number

Examples from the link provided:

  • GIF image files have the ASCII code for "GIF89a" (47 49 46 38 39 61) or "GIF87a" (47 49 46 38 37 61)
  • JPEG image files begin with FF D8 and end with FF D9. JPEG/JFIF files contain the ASCII code for "JFIF" (4A 46 49 46) as a null terminated string.
  • PNG image files begin with an 8-byte signature which identifies the file as a PNG file and allows detection of common file transfer problems: \211 P N G \r \n \032 \n (89 50 4E 47 0D 0A 1A 0A).



回答3:


I can understand your concerns, but if you look at the source of Image.FromFile method, it's just wrapper for GDI+ calls, so sadly you can't do nothing, since as I can see that bizzare choice of exception (OutOfMemoryException) was done in GDI+

So it seems that you are stuck with current code, or check file headers, but that will not guarantee that file is really an valid image.

Maybe you should consider do you really need isImageFile method in the first place ? Detect image files on extension, it would be much faster and if loading from file fails it will raise an exception so you can deal with it when you really need to load image.




回答4:


Taking the route of checking the file header, I wrote this implementation:

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

I put this into a utility/helper class along along with methods: GetFileImageTypeFromFullLoad() and GetFileImageTypeFromExtension(). The former uses my above mentioned Image.FromFile approach and the latter simply checks the file extension. I plan to use all three depending on the requirements of the situation.




回答5:


Use the System.IO.Path.GetExtension() method first to check if the Extension is an image type. Then if you want to be through you can check the headers in the file.




回答6:


here's one that uses the signatures in 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;
}


来源:https://stackoverflow.com/questions/9354747/how-can-i-determine-if-a-file-is-an-image-file-in-net

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!