Obtener dimensiones de imagen sin leer todo el archivo

¿Hay alguna forma económica de obtener las dimensiones de una imagen (jpg, png, …)? Preferiblemente, me gustaría lograr esto utilizando solo la biblioteca de clases estándar (debido a las restricciones de alojamiento). Sé que debería ser relativamente fácil leer el encabezado de la imagen y analizarlo yo mismo, pero parece que algo así debería estar ya allí. Además, he verificado que el siguiente fragmento de código lee la imagen completa (que no quiero):

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); } } } 

Su mejor apuesta como siempre es encontrar una biblioteca bien probada. Sin embargo, dijiste que es difícil, así que aquí hay un código poco fiable que no ha sido probado y debería funcionar para un buen número de casos:

 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 recognize image format."; private static Dictionary> imageFormatDecoders = new Dictionary>() { { 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 }, }; ///  /// Gets the dimensions of an image. ///  /// The path of the image to get the dimensions of. /// The dimensions of the specified image. /// The image was of an unrecognized format. 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; } } } } ///  /// Gets the dimensions of an image. ///  /// The path of the image to get the dimensions of. /// The dimensions of the specified image. /// The image was of an unrecognized format. 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); } } } 

Con suerte, el código es bastante obvio. Para agregar un nuevo formato de archivo, añádalo a imageFormatDecoders con la clave como una matriz de los "bits mágicos" que aparecen al comienzo de cada archivo del formato dado y el valor es una función que extrae el tamaño de la secuencia. La mayoría de los formatos son lo suficientemente simples, el único verdadero apestoso es jpeg.

¿Has intentado utilizar las clases de WPF Imaging? System.Windows.Media.Imaging.BitmapDecoder , etc.?

Creo que se hizo un esfuerzo para asegurarnos de que esos códecs solo lean un subconjunto del archivo para determinar la información del encabezado. Vale la pena comprobarlo.

 using (FileStream file = new FileStream(this.ImageFileName, FileMode.Open, FileAccess.Read)) { using (Image tif = Image.FromStream(stream: file, useEmbeddedColorManagement: false, validateImageData: false)) { float width = tif.PhysicalDimension.Width; float height = tif.PhysicalDimension.Height; float hresolution = tif.HorizontalResolution; float vresolution = tif.VerticalResolution; } } 

validateImageData establecido en false evita que GDI + realice un análisis costoso de los datos de imagen, lo que reduce drásticamente el tiempo de carga. Esta pregunta arroja más luz sobre el tema.

Estaba buscando algo similar unos meses antes. Quería leer el tipo, la versión, la altura y el ancho de una imagen GIF, pero no pude encontrar nada útil en línea.

Afortunadamente en el caso de GIF, toda la información requerida estaba en los primeros 10 bytes:

 Type: Bytes 0-2 Version: Bytes 3-5 Height: Bytes 6-7 Width: Bytes 8-9 

Los archivos PNG son un poco más complejos (el ancho y la altura son 4 bytes cada uno):

 Width: Bytes 16-19 Height: Bytes 20-23 

Como se mencionó anteriormente, wotsit es un buen sitio para obtener especificaciones detalladas sobre formatos de imágenes y datos, aunque las especificaciones PNG en pnglib son mucho más detalladas. Sin embargo, creo que la entrada de Wikipedia en los formatos PNG y GIF es el mejor lugar para comenzar.

Aquí está mi código original para verificar GIF, también he unido algo para PNG:

 using System; using System.IO; using System.Text; public class ImageSizeTest { public static void Main() { byte[] bytes = new byte[10]; string gifFile = @"D:\Personal\Images&Pics\iProduct.gif"; using (FileStream fs = File.OpenRead(gifFile)) { fs.Read(bytes, 0, 10); // type (3 bytes), version (3 bytes), width (2 bytes), height (2 bytes) } displayGifInfo(bytes); string pngFile = @"D:\Personal\Images&Pics\WaveletsGamma.png"; using (FileStream fs = File.OpenRead(pngFile)) { fs.Seek(16, SeekOrigin.Begin); // jump to the 16th byte where width and height information is stored fs.Read(bytes, 0, 8); // width (4 bytes), height (4 bytes) } displayPngInfo(bytes); } public static void displayGifInfo(byte[] bytes) { string type = Encoding.ASCII.GetString(bytes, 0, 3); string version = Encoding.ASCII.GetString(bytes, 3, 3); int width = bytes[6] | bytes[7] << 8; // byte 6 and 7 contain the width but in network byte order so byte 7 has to be left-shifted 8 places and bit-masked to byte 6 int height = bytes[8] | bytes[9] << 8; // same for height Console.WriteLine("GIF\nType: {0}\nVersion: {1}\nWidth: {2}\nHeight: {3}\n", type, version, width, height); } public static void displayPngInfo(byte[] bytes) { int width = 0, height = 0; for (int i = 0; i <= 3; i++) { width = bytes[i] | width << 8; height = bytes[i + 4] | height << 8; } Console.WriteLine("PNG\nWidth: {0}\nHeight: {1}\n", width, height); } } 

De acuerdo con las respuestas hasta ahora y algunas búsquedas adicionales, parece que en la biblioteca de clases .NET 2 no hay ninguna funcionalidad para ello. Así que decidí escribir el mío. Aquí hay una versión muy aproximada de esto. Por el momento, lo necesitaba solo para JPG. Por lo tanto, completa la respuesta publicada por Abbas.

No hay verificación de errores ni ninguna otra verificación, pero actualmente la necesito para una tarea limitada, y eventualmente puede agregarse fácilmente. Lo probé en algunas imágenes, y generalmente no lee más de 6K de una imagen. Supongo que depende de la cantidad de datos EXIF.

 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); } } } } 

Hice esto para el archivo PNG

  var buff = new byte[32]; using (var d = File.OpenRead(file)) { d.Read(buff, 0, 32); } const int wOff = 16; const int hOff = 20; var Widht =BitConverter.ToInt32(new[] {buff[wOff + 3], buff[wOff + 2], buff[wOff + 1], buff[wOff + 0],},0); var Height =BitConverter.ToInt32(new[] {buff[hOff + 3], buff[hOff + 2], buff[hOff + 1], buff[hOff + 0],},0); 

Sí, puede hacerlo y el código depende del formato de archivo. Trabajo para un proveedor de imágenes ( Atalasoft ) y nuestro producto proporciona GetImageInfo () para cada códec que hace lo mínimo para averiguar las dimensiones y algunos otros datos fáciles de obtener.

Si desea hacer su propia versión , le sugiero que comience con wotsit.org , que tiene especificaciones detalladas para casi todos los formatos de imagen y verá cómo identificar el archivo y también dónde se puede encontrar la información.

Si te sientes cómodo trabajando con C, entonces puedes usar el jpeglib gratuito para obtener esta información también. Apuesto a que puede hacer esto con las bibliotecas .NET, pero no sé cómo.

Va a depender del formato de archivo. Usualmente lo declararán en los primeros bytes del archivo. Y, generalmente, una buena implementación de lectura de imágenes lo tendrá en cuenta. Sin embargo, no puedo señalarle uno para .NET.