2008-09-21 22 views
94

¿Hay alguna manera 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 toda la imagen (que no quiero):Obtener dimensiones de la imagen sin leer todo el archivo

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

Sería de gran ayuda si usted fuera un poco más específico en la pregunta adecuada. Las etiquetas me han dicho .net y C#, y quieres una biblioteca estándar, pero ¿cuáles son estas restricciones de alojamiento que mencionas? – wnoise

+0

Si tiene acceso al espacio de nombres System.Windows.Media.Imaging (en WPF), consulte esta pregunta SO: http://stackoverflow.com/questions/784734/using-wpf-imaging-classes-getting-image-dimensions -with-reading-the-entire? lq = 1 – Charlie

Respuesta

96

Su mejor apuesta como siempre es encontrar una biblioteca bien probado. Sin embargo, usted ha dicho que es difícil, así que aquí hay un código no probado en gran medida poco fiables que deberían trabajar por 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<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 unrecognized 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 unrecognized 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); 
     } 
    } 
} 

Esperamos que el código es bastante obvio. Para agregar un nuevo formato de archivo, agréguelo al 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.

+6

De acuerdo, JPEG es una mierda. Por cierto, una nota para las personas que quieran usar este código en el futuro: esto no ha sido probado. Lo he pasado con un peine fino, y esto es lo que encontré: el formato BMP tiene otra variación de encabezado (antigua) donde las dimensiones son de 16 bits; más la altura puede ser negativa (soltar el letrero). En cuanto a JPEG, 0xC0 no es el único encabezado. Básicamente, todos 0xC0 a 0xCF excepto 0xC4 y 0xCC son encabezados válidos (puede obtenerlos fácilmente en JPG entrelazados). Y, para hacer las cosas más divertidas, la altura puede ser 0 y especificada más adelante en un bloque 0xDC. Ver http://www.w3.org/Graphics/JPEG/itu-t81.pdf –

+1

@ Vilx-, ¿puedes compartir el código más completo? – Pedro77

+0

Modificó el método DecodeJfif anterior para expandir la comprobación original (marcador == 0xC0) para aceptar 0xC1 y 0xC2 también. Estos otros encabezados de inicio de cuadro SOF1 y SOF2 codifican ancho/alto en las mismas posiciones de bytes. SOF2 es bastante común. –

22

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

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

+0

Gracias. Parece razonable, pero mi alojamiento tiene .NET 2. –

+1

Excelente respuesta. Si puede obtener una referencia a PresentationCore en su proyecto, este es el camino a seguir. – ojrac

+0

En mis pruebas unitarias, estas clases no funcionan mejor que GDI ... aún requieren ~ 32K para leer las dimensiones JPEG. – Nariman

-1

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.

1

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 conocer las dimensiones y algunos otros datos fáciles de obtener.

Si desea enrollar el suyo, sugiero comenzar por 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 se siente cómodo trabajando con C, entonces también se puede usar el jpeglib libre para obtener esta información. Apuesto a que puede hacer esto con las bibliotecas .NET, pero no sé cómo.

+0

¿Es seguro asumir que utilizando ['new AtalaImage (filepath) .Width'] (http://www.atalasoft.com/docs/joltimage/docs103/com/atalasoft/imaging/AtalaImage.html#AtalaImage (java.lang.String)) hace algo ¿similar? – drzaus

+0

o simplemente ['Atalasoft.Imaging.Codec.RegisteredDecoders.GetImageInfo (fullPath) .Size'] (https://www.atalasoft.com/cs/forums/thread/13163.aspx) – drzaus

+1

El primero (AtalaImage) lee el Toda la imagen: la segunda (GetImageInfo) lee los metadatos mínimos para obtener los elementos de un objeto de información de imagen. –

12

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 caso de GIF, toda la información necesaria era en los primeros 10 bytes:

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

PNG son ligeramente más complejo (anchura y 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 el control de los GIF, también he abofeteado juntos 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); 
    } 
} 
8

Según 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 comprobació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); 
      } 
     } 

    } 
} 
+0

El ancho y la altura se invierten cuando intento esto. –

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

la validateImageData conjunto a false impide GDI + de la realización de análisis costoso de los datos de imagen, disminuyendo así severamente el tiempo de carga. This question arroja más luz sobre el tema.

+1

Utilicé su solución como último recurso mezclado con la solución de ICR arriba. Tuve problemas con JPEG y lo resolví con esto. – Zorkind

+1

Hace poco probé esto en un proyecto en el que tuve que consultar el tamaño de más de 2000 imágenes (jpg y png en su mayoría, tamaños muy mixtos), y de hecho fue mucho más rápido que la forma tradicional usando 'new Bitmap()'. – AeonOfTime

2

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); 
Cuestiones relacionadas