it-swarm.com.de

Abrufen von Bildabmessungen, ohne die gesamte Datei zu lesen

Gibt es eine kostengünstige Möglichkeit, die Abmessungen eines Bildes (JPG, PNG, ...) zu ermitteln? Am liebsten würde ich dies nur mit der Standardklassenbibliothek erreichen (aus Hosting-Gründen). Ich weiß, dass es relativ einfach sein sollte, den Bildkopf zu lesen und ihn selbst zu analysieren, aber es scheint, dass so etwas schon da sein sollte. Außerdem habe ich überprüft, ob der folgende Code das gesamte Bild liest (was ich nicht möchte):

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);
        }
    }
}
102
Jan Zich

Wie immer ist es am besten, eine gut getestete Bibliothek zu finden. Sie sagten jedoch, dass dies schwierig ist. Deshalb hier ein zweifelhafter, weitgehend ungetesteter Code, der für eine ganze Reihe von Fällen funktionieren sollte:

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

Hoffentlich ist der Code ziemlich offensichtlich. Um ein neues Dateiformat hinzuzufügen, fügen Sie es zu imageFormatDecoders hinzu, wobei der Schlüssel ein Array der "magischen Bits" ist, die am Anfang jeder Datei des angegebenen Formats erscheinen, und der Wert eine Funktion ist, die die Größe extrahiert aus dem Bach. Die meisten Formate sind einfach genug, der einzige wirkliche Stinker ist JPEG.

102
ICR
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;
     }
}

die Einstellung validateImageData auf false verhindert, dass GDI + kostspielige Analysen der Bilddaten durchführt, wodurch die Ladezeit erheblich verkürzt wird. Diese Frage wirft mehr Licht auf das Thema.

23
Koray

Haben Sie versucht, die WPF-Imaging-Klassen zu verwenden? System.Windows.Media.Imaging.BitmapDecoder, etc.?

Ich glaube, dass einige Anstrengungen unternommen wurden, um sicherzustellen, dass diese Codecs nur einen Teil der Datei lesen, um die Header-Informationen zu ermitteln. Es ist einen Scheck wert.

21
Frank Krueger

Ich habe vor ein paar Monaten nach etwas Ähnlichem gesucht. Ich wollte den Typ, die Version, die Höhe und die Breite eines GIF-Bildes lesen, konnte jedoch online nichts Nützliches finden.

Glücklicherweise befanden sich im Fall von GIF alle erforderlichen Informationen in den ersten 10 Bytes:

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

PNG sind etwas komplexer (Breite und Höhe jeweils 4 Byte):

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

Wie oben erwähnt, ist wotsit eine gute Seite für detaillierte Angaben zu Bild- und Datenformaten, obwohl die PNG-Angaben unter pnglib viel detaillierter sind. Allerdings halte ich den Wikipedia-Eintrag zu PNG und GIF für den besten Ort anfangen.

Hier ist mein ursprünglicher Code zum Überprüfen von GIFs. Ich habe auch etwas für PNGs zusammengestellt:

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

Aufgrund der bisherigen Antworten und einiger zusätzlicher Suchanfragen scheint es in der .NET 2-Klassenbibliothek keine Funktionalität dafür zu geben. Also habe ich beschlossen, meine eigene zu schreiben. Hier ist eine sehr grobe Version davon. Im Moment habe ich es nur für JPGs gebraucht. Damit ist die Antwort von Abbas vollständig.

Es gibt keine Fehlerüberprüfung oder andere Überprüfung, aber ich benötige sie derzeit für eine begrenzte Aufgabe, und sie kann schließlich leicht hinzugefügt werden. Ich habe es an einer Reihe von Bildern getestet und normalerweise liest es nicht mehr als 6 KB von einem Bild. Ich denke, es hängt von der Menge der EXIF-Daten ab.

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

    }
}
8
Jan Zich

Ich habe dies für PNG-Datei gemacht

  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);
3
Danny D

Ja, das ist absolut möglich und der Code hängt vom Dateiformat ab. Ich arbeite für einen Imaging-Anbieter ( Atalasoft ), und unser Produkt bietet GetImageInfo () für jeden Codec, der das Minimum tut, um die Abmessungen zu ermitteln, und einige andere, einfach abzurufende Daten.

Wenn Sie Ihre eigene Datei rollen möchten, empfehle ich, mit wotsit.org zu beginnen, das detaillierte Spezifikationen für so ziemlich alle Bildformate enthält. Sie werden sehen, wie Sie die Datei identifizieren und wo sich Informationen darin befinden können gefunden.

Wenn Sie gerne mit C arbeiten, können Sie diese Informationen auch mit der kostenlosen jpeglib abrufen. Ich würde wetten, dass Sie dies mit .NET-Bibliotheken tun können, aber ich weiß nicht wie.

1
Lou Franco