determine if file is an image
Asked Answered
M

14

66

I am looping through a directory and copying all files. Right now I am doing string.EndsWith checks for ".jpg" or ".png", etc . .

Is there any more elegant way of determining if a file is an image (any image type) without the hacky check like above?

Materi answered 22/3, 2009 at 4:17 Comment(3)
Is there a reason the extension is not good enough?Mekong
See also: #9355247Kyl
@Mekong yes, maybe file is image but somehow renamed to something elseRockrose
T
43

Check the file for a known header. (Info from link also mentioned in this answer)

The first eight bytes of a PNG file always contain the following (decimal) values: 137 80 78 71 13 10 26 10

Tamekia answered 22/3, 2009 at 4:22 Comment(3)
Note that the second through sixth characters are "PNG\r\n"Trogon
Sorry but your link didn't work anymore. I've got a 403 error.Buttonball
The link content, originally posted July 2008, is archived on the WayBackMachinePandybat
R
42

Check out System.IO.Path.GetExtension

Here is a quick sample.

public static readonly List<string> ImageExtensions = new List<string> { ".JPG", ".JPEG", ".JPE", ".BMP", ".GIF", ".PNG" };

private void button_Click(object sender, RoutedEventArgs e)
{
    var folder = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
    var files = Directory.GetFiles(folder);
    foreach(var f in files)
    {
        if (ImageExtensions.Contains(Path.GetExtension(f).ToUpperInvariant()))
        {
            // process image
        }
    }
}
Reck answered 22/3, 2009 at 4:23 Comment(4)
He said he is checking to see if the string.EndsWith from what I gatheredReck
yeah, but it's still a string comparison. What if I rename a .jpf file to be .txt?Tamekia
There are two possible interepretations of the question; either 'EndsWith' is hacky (in which case this answer is what the OP wants), or 'using the filename' is hacky, in which case @MitchWheat's answer is what the OP wants. I prefer Mitch's, but upvoted both.Hartley
If you use the extension, be sure to convert it to lowercase before you do your comparisons. (Many digital cameras, for example, produce .JPG files.)Gallivant
M
34
System.Web.MimeMapping.GetMimeMapping(filename).StartsWith("image/");

MimeMapping.GetMimeMapping produces these results:

  • file.jpg: image/jpeg
  • file.gif: image/gif
  • file.jpeg: image/jpeg
  • file.png: image/png
  • file.bmp: image/bmp
  • file.tiff: image/tiff
  • file.svg: application/octet-stream

file.svg not returning an image/ MIME type works out in most cases because you're probably not going to process a vector image like you would a scalar image. When checking MIME type, do be aware that SVG does have the standard MIME type of image/svg+xml, even if GetMimeMapping doesn't return it.

Mitchel answered 8/6, 2016 at 23:36 Comment(1)
if the file extension is intentionally changed e.g. from .jpg to .txt, this would fail i.e the output of System.Web.MimeMapping.GetMimeMapping(filename) would be text/plainHorne
T
27

This will look at the first few bytes of a file and determine whether it is an image.

using System.Collections.Generic;
using System.IO;
using System.Linq;

public static class Extension
{
    public static bool IsImage(this Stream stream)
    {
        stream.Seek(0, SeekOrigin.Begin);

        List<string> jpg = new List<string> { "FF", "D8" };
        List<string> bmp = new List<string> { "42", "4D" };
        List<string> gif = new List<string> { "47", "49", "46" };
        List<string> png = new List<string> { "89", "50", "4E", "47", "0D", "0A", "1A", "0A" };
        List<List<string>> imgTypes = new List<List<string>> { jpg, bmp, gif, png };

        List<string> bytesIterated = new List<string>();

        for (int i = 0; i < 8; i++)
        {
            string bit = stream.ReadByte().ToString("X2");
            bytesIterated.Add(bit);

            bool isImage = imgTypes.Any(img => !img.Except(bytesIterated).Any());
            if (isImage)
            {
                return true;
            }
        }

        return false;
    }
}

Edit

I've made a few changes to the above to allow you to add your own images if you needed to, also removed collections which weren't necessary to begin with. I also added an overload accepting an out parameter of type string, setting the value to the type of image the stream is composed of.

public static class Extension
{
    static Extension()
    {
        ImageTypes = new Dictionary<string, string>();
        ImageTypes.Add("FFD8","jpg");
        ImageTypes.Add("424D","bmp");
        ImageTypes.Add("474946","gif");
        ImageTypes.Add("89504E470D0A1A0A","png");
    }
    
    /// <summary>
    ///     <para> Registers a hexadecimal value used for a given image type </para>
    ///     <param name="imageType"> The type of image, example: "png" </param>
    ///     <param name="uniqueHeaderAsHex"> The type of image, example: "89504E470D0A1A0A" </param>
    /// </summary>
    public static void RegisterImageHeaderSignature(string imageType, string uniqueHeaderAsHex)
    {
        Regex validator = new Regex(@"^[A-F0-9]+$", RegexOptions.CultureInvariant);
    
        uniqueHeaderAsHex = uniqueHeaderAsHex.Replace(" ", "");
        
        if (string.IsNullOrWhiteSpace(imageType))         throw new ArgumentNullException("imageType");
        if (string.IsNullOrWhiteSpace(uniqueHeaderAsHex)) throw new ArgumentNullException("uniqueHeaderAsHex");
        if (uniqueHeaderAsHex.Length % 2 != 0)            throw new ArgumentException    ("Hexadecimal value is invalid");
        if (!validator.IsMatch(uniqueHeaderAsHex))        throw new ArgumentException    ("Hexadecimal value is invalid");
        
        ImageTypes.Add(uniqueHeaderAsHex, imageType);
    }
    
    private static Dictionary<string, string> ImageTypes;

    public static bool IsImage(this Stream stream)
    {
        string imageType;
        return stream.IsImage(out imageType);
    }
    
    public static bool IsImage(this Stream stream, out string imageType)
    {
        stream.Seek(0, SeekOrigin.Begin);
        StringBuilder builder = new StringBuilder();
        int largestByteHeader = ImageTypes.Max(img => img.Value.Length);
        
        for (int i = 0; i < largestByteHeader; i++)
        {
            string bit = stream.ReadByte().ToString("X2");
            builder.Append(bit);
            
            string builtHex = builder.ToString();
            bool isImage = ImageTypes.Keys.Any(img => img == builtHex);
            if (isImage)
            {
                imageType = ImageTypes[builder.ToString()];
                return true;
            }
        }
        imageType = null;
        return false;
    }
}
Thorma answered 26/8, 2014 at 15:19 Comment(8)
not practical as you are missing a lot of possible image types.Mattoid
A few typos: Should be return IsImage(stream, out imageType); instead of return stream.IsImage(out imageType); Should be int largestByteHeader = ImageTypes.Max(img => img.Key.Length) instead of int largestByteHeader = ImageTypes.Max(img => img.Value.Length)Clifford
Implement what you just wrote and see if it even compilesThorma
Your edit has a minor bug on the int largestByteHeader = ImageTypes.Max(img => img.Value.Length); line - the length should be divided by 2 since each character in a hexadecimal string is only worth 4 bits not 8, and you use the result of this statement as if it is the number of bytes in the header. Take PNG for example, the header is 8 bytes but your code erroneously calculates it as 16 bytes. It still works since it just scans a longer amount than necessary in non-image cases but it makes the code confusing.Allemande
Also @an-phu was right that img.Value.Length should be img.Key.Length (you were right on the extension method not needing to be changed though).Allemande
Thanks @Aydin. This solution worked for me. Just curious about how you got those magic numbers for each image type. I would like to add JPEG and SVG. Also, Can I use the same logic for Docs and Videos? I would like to check if a file is of any of these formats as well - pdf, txt, doc, docx, mp4, and mov. Thanks in advance.Headwaiter
is there a way to check for SVG files?Naamann
Your first example should probably use SequenceEqual instead of Linq Except, so it has to match the sequence exactly. Otherwise it might just check if the numbers are there or not, despite their order.Paralyze
T
20

If you want a quick way to validate an image file before it is fully read from the file, besides comparing the file extension, you can just check its header looking for file signature (the following code IsValidImageFile() checks for BMP, GIF87a, GIF89a, PNG, TIFF, JPEG)

    /// <summary>
    /// Reads the header of different image formats
    /// </summary>
    /// <param name="file">Image file</param>
    /// <returns>true if valid file signature (magic number/header marker) is found</returns>
    private bool IsValidImageFile(string file)
    {
        byte[] buffer = new byte[8];
        byte[] bufferEnd = new byte[2];

        var bmp = new byte[] { 0x42, 0x4D };               // BMP "BM"
        var gif87a = new byte[] { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 };     // "GIF87a"
        var gif89a = new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 };     // "GIF89a"
        var png = new byte[] { 0x89, 0x50, 0x4e, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };   // PNG "\x89PNG\x0D\0xA\0x1A\0x0A"
        var tiffI = new byte[] { 0x49, 0x49, 0x2A, 0x00 }; // TIFF II "II\x2A\x00"
        var tiffM = new byte[] { 0x4D, 0x4D, 0x00, 0x2A }; // TIFF MM "MM\x00\x2A"
        var jpeg = new byte[] { 0xFF, 0xD8, 0xFF };        // JPEG JFIF (SOI "\xFF\xD8" and half next marker xFF)
        var jpegEnd = new byte[] { 0xFF, 0xD9 };           // JPEG EOI "\xFF\xD9"

        try
        {
            using (System.IO.FileStream fs = new System.IO.FileStream(file, System.IO.FileMode.Open, System.IO.FileAccess.Read))
            {
                if (fs.Length > buffer.Length)
                {
                    fs.Read(buffer, 0, buffer.Length);
                    fs.Position = (int)fs.Length - bufferEnd.Length;
                    fs.Read(bufferEnd, 0, bufferEnd.Length);
                }

                fs.Close();
            }

            if (this.ByteArrayStartsWith(buffer, bmp) ||
                this.ByteArrayStartsWith(buffer, gif87a) ||
                this.ByteArrayStartsWith(buffer, gif89a) ||
                this.ByteArrayStartsWith(buffer, png) ||
                this.ByteArrayStartsWith(buffer, tiffI) ||
                this.ByteArrayStartsWith(buffer, tiffM))
            {
                return true;
            }

            if (this.ByteArrayStartsWith(buffer, jpeg))
            {
                // Offset 0 (Two Bytes): JPEG SOI marker (FFD8 hex)
                // Offest 1 (Two Bytes): Application segment (FF?? normally ??=E0)
                // Trailer (Last Two Bytes): EOI marker FFD9 hex
                if (this.ByteArrayStartsWith(bufferEnd, jpegEnd))
                {
                    return true;
                }
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message, Lang.Lang.ErrorTitle + " IsValidImageFile()", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }

        return false;
    }

    /// <summary>
    /// Returns a value indicating whether a specified subarray occurs within array
    /// </summary>
    /// <param name="a">Main array</param>
    /// <param name="b">Subarray to seek within main array</param>
    /// <returns>true if a array starts with b subarray or if b is empty; otherwise false</returns>
    private bool ByteArrayStartsWith(byte[] a, byte[] b)
    {
        if (a.Length < b.Length)
        {
            return false;
        }

        for (int i = 0; i < b.Length; i++)
        {
            if (a[i] != b[i])
            {
                return false;
            }
        }

        return true;
    }

Checking the header signature can be fast, since it does not load the whole file or create large objects, specially when processing several files. But it does not check if the rest of the data is well formed. To do so, a second step to try to load the file to an Image object can be done (and this way be certain that the file can be displayed and handled by your program).

bool IsValidImage(string filename)
{
    try
    {
        using(Image newImage = Image.FromFile(filename))
        {}
    }
    catch (OutOfMemoryException ex)
    {
        //The file does not have a valid image format.
        //-or- GDI+ does not support the pixel format of the file

        return false;
    }
    return true;
}
Tengler answered 6/4, 2018 at 1:37 Comment(1)
What is "bufferEnd " ? Seems it reads from the end of the file? This might not always be possible when the file is not of the file system (needs a lot of skipping), no? Also, what about WEBP ? And the new HEIC ?Hexangular
W
15

We can use Image and graphics classes from namespace System.Drawing; to do our job. If the code works without error it is an image, else it is not. That is let the DotNet framework do the job for us. The code -

public string CheckFile(file)
{
    string result="";
    try
    {
        System.Drawing.Image imgInput = System.Drawing.Image.FromFile(file);
        System.Drawing.Graphics gInput = System.Drawing.Graphics.fromimage(imgInput);  
        Imaging.ImageFormat thisFormat = imgInput.RawFormat;   
        result="It is image";        
    }
    catch(Exception ex)
    {
        result="It is not image"; 
    }
    return result;
}
Wakeup answered 5/7, 2015 at 11:40 Comment(2)
Add this code to try section: imgInput.Dispose(); gInput.Dispose(); . Unless the file handle will be open and other processes can not use it.Term
Take care: while this works it will use system resources and memory. Ok for a few image checks here and there, but not for a web environment where people can upload images of any size.Vicentevicepresident
L
8

I use the following method. It uses the built in Image decoder to retrieve a list of extensions that the system recognises as image files, then compares those extensions to the extension of the file name you pass in. Returns a simple TRUE/FALSE.

public static bool IsRecognisedImageFile(string fileName)
{
    string targetExtension = System.IO.Path.GetExtension(fileName);
    if (String.IsNullOrEmpty(targetExtension))
        return false;
    else
        targetExtension = "*" + targetExtension.ToLowerInvariant();

    List<string> recognisedImageExtensions = new List<string>();

    foreach (System.Drawing.Imaging.ImageCodecInfo imageCodec in System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders())
        recognisedImageExtensions.AddRange(imageCodec.FilenameExtension.ToLowerInvariant().Split(";".ToCharArray()));

    foreach (string extension in recognisedImageExtensions)
    {
        if (extension.Equals(targetExtension))
        {
            return true;
        }
    }
    return false;
}
Lindblad answered 5/6, 2014 at 8:21 Comment(1)
This works well as partial replacement for https://mcmap.net/q/277553/-determine-if-uploaded-file-is-image-any-format-on-mvcToshiatoshiko
R
7

See if this helps.

EDIT: Also, Image.FromFile(....).RawFormat might help. It could throw an exception if the file is not an image.

Rowen answered 22/3, 2009 at 4:36 Comment(0)
E
2

Not exactly the answer you need. But if it’s the Internet then MIME type.

Ency answered 22/3, 2009 at 4:53 Comment(0)
F
2

I'm not sure what the performance drawback would be for this solution but couldn't you perform some image function on the file in a try block that would fail and fall to a catch block if it is not an image?

This strategy may not be the best solution in all situations but in the case that I am currently working with it has one major advantage: You can use whatever function that you plan to use to process the image (if it is an image) for the test function. In that way you can test all current image types but it would also extend to future image types without adding that new image extension to your supported image type list.

Does anyone see any drawbacks to this strategy?

Fanjet answered 1/10, 2012 at 14:5 Comment(1)
Purposely using try/catch is not desired as exceptions are / can be expensive (over-head wise) and it has a slight code smell to it. This is not an elegant solution. Please see dylmcc for the best solution. I have used it and it works. I can see no draw backs to that solution..Mattoid
C
1

This is a tricky one. If file is not an Image an exception will thrown. From that we can check the file is image or not.

        using (Stream stream = File.OpenRead(file))
           {
               try
               {
                   using (Image sourceImage = Image.FromStream(stream, false, false))
                   {

                   }
               }
               catch (Exception x)
               {
                   if (x.Message.Contains("not valid"))
                   {
                     Console.Write("This is not a Image.");
                   }

               }
           }
Cottonwood answered 14/8, 2019 at 3:54 Comment(0)
C
0

This is what I use - it is just a tweak of @dylmcc's answer to make it a bit more readable.

public static bool IsRecognisedImageFile(string fileName)
{
    string targetExtension = System.IO.Path.GetExtension(fileName);
    if (String.IsNullOrEmpty(targetExtension))
    {
        return false;
    }

    var recognisedImageExtensions = System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders().SelectMany(codec => codec.FilenameExtension.ToLowerInvariant().Split(';'));

    targetExtension = "*" + targetExtension.ToLowerInvariant();
    return recognisedImageExtensions.Contains(targetExtension);
}
Chiastic answered 25/6, 2019 at 0:6 Comment(2)
-1. The question is how to test a file not a filename. You can have text files saved as .png or indeed corrupted image files that will not open as an image.Religiose
That's one way to interpret the question - there are of course trade offs between trusting the extension as a proxy for reality vs actually attempting to work with the image using whatever tools are at hand.Chiastic
N
0

First you should check on the file mime type then after that check if it contains image/ in it that's the easiest way in Asp.net core

  public bool CheckFileIsImage(string? fileName)
    {
        var provider = new Microsoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider();
        provider.TryGetContentType(fileName, out string? contentType);

        return contentType?.Contains("image/") ?? false;
    }
Nun answered 16/3 at 15:36 Comment(0)
O
-3

My simple code

public static List<string> GetAllPhotosExtensions()
    {
        var list = new List<string>();
        list.Add(".jpg");
        list.Add(".png");
        list.Add(".bmp");
        list.Add(".gif");
        list.Add(".jpeg");
        list.Add(".tiff");
        return list;
    }

Check if image file

public static bool IsPhoto(string fileName)
    {
        var list = FileListExtensions.GetAllPhotosExtensions();
        var filename= fileName.ToLower();
        bool isThere = false;
        foreach(var item in list)
        {
            if (filename.EndsWith(item))
            {
                isThere = true;
                break;
            }
        }
        return isThere;     
    }
Oriana answered 13/6, 2018 at 9:12 Comment(1)
Type of file is determined by signature. Not by file extension.Estrone

© 2022 - 2024 — McMap. All rights reserved.