Auto Detecting Image Type and Extension from Byte[] in C#

When working with byte arrays from databases, often you're actually working with images. The following class allows you to detect if an array actually is an image and - if so - what the suitable file extension would be. Usage is straightforward:

// val is a byte[] from the database
string fileExtension = PictureHelper.TryGetExtension(val);

// check if a valid file extension was found
if (fileExtension != null)
{
    // it is a valid image file, write it to disk for further processing
    string fileName = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + "." + fileExtension);
    File.WriteAllBytes(fileName, val);
}

The class can easily be extended for more file formats. It uses a lookup for the typical "magic bytes" that are used for different image types. An overview of common file signatures can also be found at Wikipedia. Files starting with "<?xml>" are further analyzed - the class checks the first kilobyte for an actual SVG starting tag. This limit can easily be finetuned by adapting "maxReadCount".

public static class PictureHelper
{
    // some magic bytes for the most important image formats, see Wikipedia for more
    static readonly List<byte> jpg = new List<byte> { 0xFF, 0xD8 };
    static readonly List<byte> bmp = new List<byte> { 0x42, 0x4D };
    static readonly List<byte> gif = new List<byte> { 0x47, 0x49, 0x46 };
    static readonly List<byte> png = new List<byte> { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
    static readonly List<byte> svg_xml_small = new List<byte> { 0x3C, 0x3F, 0x78, 0x6D, 0x6C }; // "<?xml"
    static readonly List<byte> svg_xml_capital = new List<byte> { 0x3C, 0x3F, 0x58, 0x4D, 0x4C }; // "<?XML"
    static readonly List<byte> svg_small = new List<byte> { 0x3C, 0x73, 0x76, 0x67}; // "<svg"
    static readonly List<byte> svg_capital = new List<byte> { 0x3C, 0x53, 0x56, 0x47 }; // "<SVG"
    static readonly List<byte> intel_tiff = new List<byte> { 0x49, 0x49, 0x2A, 0x00};
    static readonly List<byte> motorola_tiff = new List<byte> { 0x4D, 0x4D, 0x00, 0x2A};

    static readonly List< (List<byte> magic, string extension)> imageFormats = new List<(List<byte> magic, string extension)>() 
    {
        (jpg, "jpg"),
        (bmp, "bmp"),
        (gif, "gif"),
        (png, "png"),
        (svg_small, "svg"),
        (svg_capital, "svg"),
        (intel_tiff,"tif"),
        (motorola_tiff, "tif"),
        (svg_xml_small, "svg"),
        (svg_xml_capital, "svg")
    };

    public static string TryGetExtension(Byte[] array)
    {
        // check for simple formats first
        foreach (var imageFormat in imageFormats)
        {
            if (array.IsImage(imageFormat.magic))
            {
                if (imageFormat.magic != svg_xml_small && imageFormat.magic != svg_xml_capital)
                    return imageFormat.extension;

                // special handling for SVGs starting with XML tag
                int readCount = imageFormat.magic.Count; // skip XML tag
                int maxReadCount = 1024;

                do
                {
                    if (array.IsImage(svg_small, readCount) || array.IsImage(svg_capital, readCount))
                    {
                        return imageFormat.extension;
                    }
                    readCount++;
                }
                while (readCount < maxReadCount && readCount < array.Length - 1);

                return null;
            }
        }
        return null;
    }

    private static bool IsImage(this Byte[] array, List<byte> comparer, int offset = 0)
    {
        int arrayIndex = offset;
        foreach (byte c in comparer)
        {
            if (arrayIndex > array.Length -1 || array[arrayIndex] != c)
                return false;
            ++arrayIndex;
        }
        return true;
    }
}