Dosyanın tamamını okumadan görüntü boyutlarını elde etme


104

Bir görüntünün (jpg, png, ...) boyutlarını almanın ucuz bir yolu var mı? Tercihen, bunu yalnızca standart sınıf kitaplığını kullanarak (barındırma kısıtlamaları nedeniyle) elde etmek istiyorum. Resim başlığını okumanın ve onu kendim ayrıştırmanın nispeten kolay olması gerektiğini biliyorum, ama öyle görünüyor ki böyle bir şey zaten orada olmalı. Ayrıca, aşağıdaki kod parçasının görüntünün tamamını okuduğunu doğruladım (ki istemiyorum):

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

Doğru soruda biraz daha spesifik olsaydınız yardımcı olurdu. Etiketler bana .net ve c # dedi ve siz standart kitaplık istiyorsunuz, ancak bahsettiğiniz bu barındırma kısıtlamaları nelerdir?
wnoise

System.Windows.Media.Imaging ad alanına (WPF'de) erişiminiz varsa, şu SO sorusuna bakın: stackoverflow.com/questions/784734/…
Charlie

Yanıtlar:


106

Her zamanki gibi en iyi bahsiniz, iyi test edilmiş bir kitaplık bulmaktır. Bununla birlikte, bunun zor olduğunu söylediniz, bu yüzden işte oldukça fazla sayıda durumda çalışması gereken, büyük ölçüde denenmemiş bazı tehlikeli kodlar:

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

Umarım kod oldukça açıktır. Yeni bir dosya formatı eklemek için, onu imageFormatDecoders, verilen formattaki her dosyanın başında görünen bir "sihirli bitler" dizisi ve değer, boyutu akıştan çıkaran bir işlev olacak şekilde eklersiniz . Çoğu format yeterince basittir, tek gerçek sorun jpeg'dir.


6
Kabul edildi, JPEG berbat. Btw - gelecekte bu kodu kullanmak isteyenler için bir not: bu gerçekten denenmemiş. İyi bir tarakla geçtim ve bulduğum şey şu: BMP formatında, boyutların 16 bit olduğu başka bir (eski) başlık varyasyonu var; artı yükseklik negatif olabilir (daha sonra işareti bırakın). JPEG'e gelince - 0xC0 tek başlık değildir. Temel olarak, 0xC4 ve 0xCC dışındaki tüm 0xC0 ila 0xCF geçerli üstbilgilerdir (bunları taramalı JPG'lerde kolayca alabilirsiniz). Ve işleri daha eğlenceli hale getirmek için yükseklik 0 olabilir ve daha sonra bir 0xDC bloğunda belirtilebilir. Bkz. W3.org/Graphics/JPEG/itu-t81.pdf
Vilx

Orijinal (marker == 0xC0) kontrolünü 0xC1 ve 0xC2'yi kabul edecek şekilde genişletmek için yukarıdaki DecodeJfif yönteminde ince ayar yapıldı. Bu diğer çerçeve başlangıcı başlıkları (SOF1 ve SOF2) genişliği / yüksekliği aynı bayt konumlarında kodlar. SOF2 oldukça yaygındır.
Ryan Barton

4
Standart uyarı: Asla throw e;yazmamalı, throw;bunun yerine basitçe yazmalısınız . GetDimensionspathbinaryReader
İkinciye

1
Ayrıca, bu kod birçok dijital kamera tarafından çıkarılan EXIF ​​/ TIFF biçiminde kodlanmış JPEG'leri kabul etmiyor gibi görünüyor. Yalnızca JFIF'i destekler.
2018

2
System.Drawing.Image.FromStream (stream, false, false), tüm görüntüyü yüklemeden size boyutları verir ve .Net'in yükleyebileceği herhangi bir görüntü üzerinde çalışır. Bu karmaşık ve eksik çözümün neden bu kadar çok olumlu oyu olduğu anlaşılmanın ötesinde.
dynamichael

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

validateImageDataset false, böylece ciddi bir yükleme süresini azaltarak, resim verilerinin pahalı analizi yapan gelen önler GDI +. Bu soru konuya daha fazla ışık tutuyor.


1
Sizin çözümünüzü yukarıdaki ICR çözümüyle karıştırılmış son kaynak olarak kullandım. JPEG ile sorunlar yaşadım ve bununla çözüldü.
Zorkind

2
Bunu son zamanlarda 2000'den fazla görüntünün boyutunu (çoğunlukla jpg ve png, çok karışık boyutlar) sorgulamak zorunda kaldığım bir projede denedim ve gerçekten de geleneksel yöntemden çok daha hızlıydı new Bitmap().
AeonOfTime

1
En iyi cevap. Hızlı, temiz ve etkili.
dynamichael

1
Bu işlev, pencerelerde mükemmeldir. ancak linux üzerinde çalışmıyor, yine de linux üzerindeki tüm dosyayı okuyacaktır. (.net çekirdek 2.2)
zhengchun

21

WPF Görüntüleme sınıflarını kullanmayı denediniz mi? System.Windows.Media.Imaging.BitmapDecoder, vb.?

Başlık bilgisini belirlemek için bu codec bileşenlerinin dosyanın yalnızca bir alt kümesini okumasını sağlamak için biraz çaba sarf edildiğine inanıyorum. Kontrol etmeye değer.


Teşekkür ederim. Makul görünüyor, ancak barındırmamda .NET 2 var.
Jan Zich

1
Mükemmel cevap. Projenizde PresentationCore'a bir referans alabilirseniz, gitmenin yolu budur.
ojrac

Birim testlerimde, bu sınıflar GDI'dan daha iyi performans göstermiyor ... JPEG boyutlarını okumak için hala ~ 32K gerekiyor.
Nariman

Öyleyse, OP'nin görüntü boyutlarını elde etmek için BitmapDecoder'ı nasıl kullanacaksınız?
Chuck Savage


12

Birkaç ay önce benzer bir şey arıyordum. Bir GIF resminin türünü, sürümünü, yüksekliğini ve genişliğini okumak istedim, ancak çevrimiçi olarak yararlı bir şey bulamadım.

Neyse ki GIF durumunda, gerekli tüm bilgiler ilk 10 bayt içindeydi:

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

PNG biraz daha karmaşıktır (genişlik ve yükseklik her biri 4 bayttır):

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

Yukarıda belirtildiği gibi, wotsit en PNG gözlük olsa görüntü ve veri formatları ayrıntıları özellikleri için iyi bir sitedir pnglib çok daha ayrıntılı olarak açıklanmıştır. Ancak, PNG ve GIF formatlarındaki Wikipedia girişinin başlamak için en iyi yer olduğunu düşünüyorum.

İşte GIF'leri kontrol etmek için orijinal kodum, ayrıca PNG'ler için de bir şeyler tokatladım:

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

Şimdiye kadarki cevaplara ve bazı ek aramalara dayanarak, .NET 2 sınıf kitaplığında bunun için hiçbir işlevsellik olmadığı görülmektedir. Ben de kendim yazmaya karar verdim. İşte bunun çok kaba bir versiyonu. Şu anda sadece JPG'ler için ihtiyacım vardı. Yani Abbas'ın gönderdiği cevabı tamamlıyor.

Hata kontrolü veya başka bir doğrulama yok, ancak şu anda sınırlı bir görev için ihtiyacım var ve sonunda kolayca eklenebilir. Birkaç görüntü üzerinde test ettim ve genellikle bir görüntüden 6K'dan fazla okumuyor. Sanırım bu, EXIF ​​verilerinin miktarına bağlı.

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

    }
}

Bunu denediğimde genişlik ve yükseklik tersine dönüyor.
Jason Sturges

@JasonSturges Exif Oryantasyon etiketini hesaba katmanız gerekebilir.
Andrew Morton

3

Bunu PNG dosyası için yaptım

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

1

Evet, bunu kesinlikle yapabilirsiniz ve kod dosya formatına bağlıdır. Bir görüntüleme satıcısı ( Atalasoft ) için çalışıyorum ve ürünümüz, boyutları bulmak için minimum ve bazı diğer verileri elde etmek için minimum olan her kodek için bir GetImageInfo () sağlıyor.

Kendinizinkini almak istiyorsanız, hemen hemen tüm resim formatları için ayrıntılı özelliklere sahip olan wotsit.org ile başlamanızı öneririm ve dosyayı nasıl tanımlayacağınızı ve ayrıca içindeki bilgilerin nerede bulunabileceğini göreceksiniz.

C ile rahat çalışıyorsanız, bu bilgiyi almak için ücretsiz jpeglib de kullanılabilir. Bunu .NET kitaplıkları ile yapabileceğinize bahse girerim ama nasıl yapılacağını bilmiyorum.


kullanmanın new AtalaImage(filepath).Widthbenzer bir şey yaptığını varsaymak güvenli midir?
drzaus


1
Birincisi (AtalaImage) görüntünün tamamını okur - ikincisi (GetImageInfo) bir görüntü bilgi nesnesinin öğelerini almak için minimum meta verileri okur.
Lou Franco

0

ICR'nin progresif jPeg'leri ve WebP'yi de destekleyecek yanıtı güncellendi :)

internal 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 },
        { new byte[] { 0x52, 0x49, 0x46, 0x46 }, DecodeWebP },
    };

    /// <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(StartsWith(magicBytes, kvPair.Key))
                {
                    return kvPair.Value(binaryReader);
                }
            }
        }

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

    private static bool StartsWith(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(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(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 = ReadLittleEndianInt32(binaryReader);
        int height = ReadLittleEndianInt32(binaryReader);
        return new Size(width, height);
    }

    private static Size DecodeJfif(BinaryReader binaryReader)
    {
        while(binaryReader.ReadByte() == 0xff)
        {
            byte marker = binaryReader.ReadByte();
            short chunkLength = ReadLittleEndianInt16(binaryReader);
            if(marker == 0xc0 || marker == 0xc2) // c2: progressive
            {
                binaryReader.ReadByte();
                int height = ReadLittleEndianInt16(binaryReader);
                int width = ReadLittleEndianInt16(binaryReader);
                return new Size(width, height);
            }

            if(chunkLength < 0)
            {
                ushort uchunkLength = (ushort)chunkLength;
                binaryReader.ReadBytes(uchunkLength - 2);
            }
            else
            {
                binaryReader.ReadBytes(chunkLength - 2);
            }
        }

        throw new ArgumentException(errorMessage);
    }

    private static Size DecodeWebP(BinaryReader binaryReader)
    {
        binaryReader.ReadUInt32(); // Size
        binaryReader.ReadBytes(15); // WEBP, VP8 + more
        binaryReader.ReadBytes(3); // SYNC

        var width = binaryReader.ReadUInt16() & 0b00_11111111111111; // 14 bits width
        var height = binaryReader.ReadUInt16() & 0b00_11111111111111; // 14 bits height

        return new Size(width, height);
    }

}

-1

Dosya formatına bağlı olacaktır. Genellikle dosyanın ilk baytlarında belirteceklerdir. Ve genellikle, iyi bir resim okuma uygulaması bunu hesaba katacaktır. Yine de .NET için bir tanesine işaret edemem.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.