views:

2496

answers:

7

Is there a cheap way to get the dimensions of an image (jpg, png, ...)? Preferably, I would like to achieve this using only the standard class library (because of hosting restrictions). I know that it should be relatively easy to read the image header and parse it myself, but it seems that something like this should be already there. Also, I’ve verified that the following piece of code reads the entire image (which I don’t want):

using System;
using System.Drawing;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            Image img = new Bitmap("test.png");
            System.Console.WriteLine(img.Width + " x " + img.Height);
        }
    }
}
+8  A: 

Have you tried using the WPF Imaging classes? System.Windows.Media.Imaging.BitmapDecoder, etc.?

I believe some effort was into making sure those codecs only read a subset of the file in order to determine header information. It's worth a check.

Frank Krueger
Thank you. It seems reasonable, but my hosting has .NET 2.
Jan Zich
Excellent answer. If you can get a reference to PresentationCore in your project, this is the way to go.
ojrac
A: 

It's going to depend on the file format. Usually they will state it up in the early bytes of the file. And, usually, a good image-reading implementation will take that into account. I can't point you to one for .NET though.

Kevin Conner
A: 

Yes, you can absolutely do this and the code depends on the file format. I work for an imaging vendor (Atalasoft), and our product provides a GetImageInfo() for every codec that does the minimum to find out dimensions and some other easy to get data.

If you want to roll your own, I suggest starting with wotsit.org, which has detailed specs for pretty much all image formats and you will see how to identify the file and also where information in it can be found.

If you are comfortable working with C, then the free jpeglib can be used to get this information too. I would bet that you can do this with .NET libraries, but I don't know how.

Lou Franco
+3  A: 

I was looking for something similar a few months earlier. I wanted to read the type, version, height and width of a GIF image but couldn’t find anything useful online.

Fortunately in case of GIF, all this information is easy to get hold of:
Type: Bytes 1-3
Version: Bytes 4-7
Height: Bytes 8-9
Width: Bytes 10-11

PNG are slightly more complex, width and height are 4-bytes each and are located at bytes 16-19 and 20-23 respectively.

As mentioned above wotsit is a good site for detailed specs on image and data formats, however, the PNG specs at pnglib are much more detailed.

Here’s my original code for checking GIFs, I have also slapped together something for PNGs, hope it will be useful after cleanup.

[Warning: ugly, undocumented code]

public class ImageSizeTest
{
    public static void Main()
    {         // For Gifs

 FileStream f = new FileStream("iProduct.gif", FileMode.Open, FileAccess.Read);

 displayGifInfo(f);

 f.Close();
 f = null;

 Console.WriteLine("");

 // For Pngs
 f = new FileStream("WaveletsGamma.png", FileMode.Open, FileAccess.Read);

 displayPngInfo(f);

 f.Close();
 f = null;
}


public static void displayGifInfo(FileStream f)
{
 string type = ((char)f.ReadByte()).ToString();
 type += ((char)f.ReadByte()).ToString();
 type += ((char)f.ReadByte()).ToString();

 string version = ((char)f.ReadByte()).ToString();
 version += ((char)f.ReadByte()).ToString();
 version += ((char)f.ReadByte()).ToString(); 

 int lower = f.ReadByte();
 int upper = f.ReadByte();
 int width = lower | upper << 8;

 lower = f.ReadByte();
 upper = f.ReadByte();
 int height = lower | upper << 8;  

 Console.WriteLine("GIF\nType: " + type + "\nVersion: " + version + "\nWidth: " + width + "\nHeight: " + height);
}

public static void displayPngInfo(FileStream f)
{
 byte[] size = new byte[24];

 f.Read(size, 0, 24);  

 long width = 0;

 width = width | size[16];
 width = width << 8;
 width = width | size[17];
 width = width << 8;
 width = width | size[18];
 width = width << 8;
 width = width | size[19];  

 long height = 0;

 height = height | size[20];
 height = height << 8;
 height = height | size[21];
 height = height << 8;
 height = height | size[22];
 height = height << 8;
 height = height | size[23];


 Console.WriteLine("PNG\nWidth: " + width + "\nHeight: " + height); 
}

}

Abbas
+9  A: 

Your best bet as always is to find a well tested library. However, you said that is difficult, so here is some dodgy largely untested code that should work for a fair number of cases:

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

namespace ImageDimensions
{
    public static class ImageHelper
    {
        const string errorMessage = "Could not recognise image format.";

        private static Dictionary<byte[], Func<BinaryReader, Size>> imageFormatDecoders = new Dictionary<byte[], Func<BinaryReader, Size>>()
        {
            { new byte[]{ 0x42, 0x4D }, DecodeBitmap},
            { new byte[]{ 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 }, DecodeGif },
            { new byte[]{ 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 }, DecodeGif },
            { new byte[]{ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }, DecodePng },
            { new byte[]{ 0xff, 0xd8 }, DecodeJfif },
        };

        /// <summary>
        /// Gets the dimensions of an image.
        /// </summary>
        /// <param name="path">The path of the image to get the dimensions of.</param>
        /// <returns>The dimensions of the specified image.</returns>
        /// <exception cref="ArgumentException">The image was of an unrecognised format.</exception>
        public static Size GetDimensions(string path)
        {
            using (BinaryReader binaryReader = new BinaryReader(File.OpenRead(path)))
            {
                try
                {
                    return GetDimensions(binaryReader);
                }
                catch (ArgumentException e)
                {
                    if (e.Message.StartsWith(errorMessage))
                    {
                        throw new ArgumentException(errorMessage, "path", e);
                    }
                    else
                    {
                        throw e;
                    }
                }
            }
        }

        /// <summary>
        /// Gets the dimensions of an image.
        /// </summary>
        /// <param name="path">The path of the image to get the dimensions of.</param>
        /// <returns>The dimensions of the specified image.</returns>
        /// <exception cref="ArgumentException">The image was of an unrecognised format.</exception>    
        public static Size GetDimensions(BinaryReader binaryReader)
        {
            int maxMagicBytesLength = imageFormatDecoders.Keys.OrderByDescending(x => x.Length).First().Length;

            byte[] magicBytes = new byte[maxMagicBytesLength];

            for (int i = 0; i < maxMagicBytesLength; i += 1)
            {
                magicBytes[i] = binaryReader.ReadByte();

                foreach(var kvPair in imageFormatDecoders)
                {
                    if (magicBytes.StartsWith(kvPair.Key))
                    {
                        return kvPair.Value(binaryReader);
                    }
                }
            }

            throw new ArgumentException(errorMessage, "binaryReader");
        }

        private static bool StartsWith(this byte[] thisBytes, byte[] thatBytes)
        {
            for(int i = 0; i < thatBytes.Length; i+= 1)
            {
                if (thisBytes[i] != thatBytes[i])
                {
                    return false;
                }
            }
            return true;
        }

        private static short ReadLittleEndianInt16(this BinaryReader binaryReader)
        {
            byte[] bytes = new byte[sizeof(short)];
            for (int i = 0; i < sizeof(short); i += 1)
            {
                bytes[sizeof(short) - 1 - i] = binaryReader.ReadByte();
            }
            return BitConverter.ToInt16(bytes, 0);
        }

        private static int ReadLittleEndianInt32(this BinaryReader binaryReader)
        {
            byte[] bytes = new byte[sizeof(int)];
            for (int i = 0; i < sizeof(int); i += 1)
            {
                bytes[sizeof(int) - 1 - i] = binaryReader.ReadByte();
            }
            return BitConverter.ToInt32(bytes, 0);
        }

        private static Size DecodeBitmap(BinaryReader binaryReader)
        {
            binaryReader.ReadBytes(16);
            int width = binaryReader.ReadInt32();
            int height = binaryReader.ReadInt32();
            return new Size(width, height);
        }

        private static Size DecodeGif(BinaryReader binaryReader)
        {
            int width = binaryReader.ReadInt16();
            int height = binaryReader.ReadInt16();
            return new Size(width, height);
        }

        private static Size DecodePng(BinaryReader binaryReader)
        {
            binaryReader.ReadBytes(8);
            int width = binaryReader.ReadLittleEndianInt32();
            int height = binaryReader.ReadLittleEndianInt32();
            return new Size(width, height);
        }

        private static Size DecodeJfif(BinaryReader binaryReader)
        {
            while (binaryReader.ReadByte() == 0xff)
            {
                byte marker = binaryReader.ReadByte();
                short chunkLength = binaryReader.ReadLittleEndianInt16();

                if (marker == 0xc0)
                {
                    binaryReader.ReadByte();

                    int height = binaryReader.ReadLittleEndianInt16();
                    int width = binaryReader.ReadLittleEndianInt16();
                    return new Size(width, height);
                }

                binaryReader.ReadBytes(chunkLength - 2);
            }

            throw new ArgumentException(errorMessage);
        }
    }
}

Hopefully the code is fairly obvious. To add a new file format you add it to imageFormatDecoders with the key being an array of the "magic bits" which appear at the begining of every file of the given format and the value being a function which extracts the size from the stream. Most formats are simple enough, the only real stinker is jpeg.

ICR
+3  A: 

Based on the answers so far and some additional searching, it seems that in the .NET 2 class library there is no functionality for it. So I decided to write my own. Here is a very rough version of it. At the moment, I needed it only for JPG’s. So it completes the answer posted by Abbas.

There is no error checking or any other verification, but I currently need it for a limited task, and it can be eventually easily added. I tested it on some number of images, and it usually does not read more that 6K from an image. I guess it depends on the amount of the EXIF data.

using System;
using System.IO;

namespace Test
{

    class Program
    {

        static bool GetJpegDimension(
            string fileName,
            out int width,
            out int height)
        {

            width = height = 0;
            bool found = false;
            bool eof = false;

            FileStream stream = new FileStream(
                fileName,
                FileMode.Open,
                FileAccess.Read);

            BinaryReader reader = new BinaryReader(stream);

            while (!found || eof)
            {

                // read 0xFF and the type
                reader.ReadByte();
                byte type = reader.ReadByte();

                // get length
                int len = 0;
                switch (type)
                {
                    // start and end of the image
                    case 0xD8: 
                    case 0xD9: 
                        len = 0;
                        break;

                    // restart interval
                    case 0xDD: 
                        len = 2;
                        break;

                    // the next two bytes is the length
                    default: 
                        int lenHi = reader.ReadByte();
                        int lenLo = reader.ReadByte();
                        len = (lenHi << 8 | lenLo) - 2;
                        break;
                }

                // EOF?
                if (type == 0xD9)
                    eof = true;

                // process the data
                if (len > 0)
                {

                    // read the data
                    byte[] data = reader.ReadBytes(len);

                    // this is what we are looking for
                    if (type == 0xC0)
                    {
                        width = data[1] << 8 | data[2];
                        height = data[3] << 8 | data[4];
                        found = true;
                    }

                }

            }

            reader.Close();
            stream.Close();

            return found;

        }

        static void Main(string[] args)
        {
            foreach (string file in Directory.GetFiles(args[0]))
            {
                int w, h;
                GetJpegDimension(file, out w, out h);
                System.Console.WriteLine(file + ": " + w + " x " + h);
            }
        }

    }
}
Jan Zich
A: 

Is there a way to find out about a png image transparency by reading the image header?

Uma Nagarajan