Nhận kích thước hình ảnh mà không cần đọc toàn bộ tệp


104

Có cách nào rẻ để lấy kích thước của hình ảnh (jpg, png, ...) không? Tốt hơn là tôi muốn đạt được điều này chỉ bằng cách sử dụng thư viện lớp tiêu chuẩn (vì các hạn chế về lưu trữ). Tôi biết rằng nó sẽ tương đối dễ dàng để đọc tiêu đề hình ảnh và tự phân tích nó, nhưng có vẻ như một cái gì đó như thế này đã có sẵn. Ngoài ra, tôi đã xác minh rằng đoạn mã sau đọc toàn bộ hình ảnh (mà tôi không muốn):

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

Sẽ hữu ích nếu bạn cụ thể hơn một chút trong câu hỏi thích hợp. Các thẻ đã cho tôi biết .net và c #, và bạn muốn có thư viện chuẩn, nhưng những hạn chế lưu trữ này mà bạn đề cập là gì?
wnoise 21/09/08

Nếu bạn có quyền truy cập vào không gian tên System.Windows.Media.Imaging (trong WPF), hãy xem câu hỏi SO này: stackoverflow.com/questions/784734/…
Charlie

Câu trả lời:


106

Đặt cược tốt nhất của bạn như mọi khi là tìm một thư viện đã được kiểm tra tốt. Tuy nhiên, bạn nói rằng điều đó là khó, vì vậy đây là một số mã tinh vi chưa được kiểm tra phần lớn sẽ hoạt động cho một số trường hợp:

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

Hy vọng rằng mã là khá rõ ràng. Để thêm định dạng tệp mới, bạn thêm nó vào imageFormatDecodersvới khóa là một mảng "bit ma thuật" xuất hiện ở đầu mỗi tệp có định dạng đã cho và giá trị là một hàm trích kích thước từ luồng. Hầu hết các định dạng đều đủ đơn giản, yếu tố gây hôi thối thực sự duy nhất là jpeg.


6
Đồng ý, JPEG tệ. Btw - một lưu ý cho những người muốn sử dụng mã này trong tương lai: điều này thực sự chưa được kiểm tra. Tôi đã xem qua nó với một cái lược tốt và đây là những gì tôi tìm thấy: Định dạng BMP có một biến thể tiêu đề (cổ) ​​khác trong đó kích thước là 16-bit; cộng với chiều cao có thể là số âm (bỏ dấu sau đó). Đối với JPEG - 0xC0 không phải là tiêu đề duy nhất. Về cơ bản, tất cả 0xC0 đến 0xCF ngoại trừ 0xC4 và 0xCC đều là các tiêu đề hợp lệ (bạn có thể dễ dàng lấy chúng trong các JPG xen kẽ). Và, để làm cho mọi thứ trở nên thú vị hơn, chiều cao có thể bằng 0 và được chỉ định sau đó trong một khối 0xDC. Xem w3.org/Graphics/JPEG/itu-t81.pdf
Vilx-

Đã chỉnh sửa phương thức DecodeJfif ở trên để mở rộng kiểm tra ban đầu (điểm đánh dấu == 0xC0) để chấp nhận cả 0xC1 và 0xC2. Các tiêu đề đầu khung khác này SOF1 và SOF2 mã hóa chiều rộng / chiều cao ở các vị trí byte giống nhau. SOF2 khá phổ biến.
Ryan Barton

4
Cảnh báo tiêu chuẩn: Bạn không bao giờ nên viết throw e;mà hãy đơn giản throw;thay thế. Ý kiến của Bạn XML doc ngày thứ hai GetDimensionscũng cho thấy paththay vìbinaryReader
Eregrith

1
Ngoài ra, có vẻ như mã này không chấp nhận JPEG được mã hóa ở định dạng EXIF ​​/ TIFF được xuất bởi nhiều máy ảnh kỹ thuật số. Nó chỉ hỗ trợ JFIF.
cwills

2
System.Drawing.Image.FromStream (stream, false, false) sẽ cung cấp cho bạn kích thước mà không cần tải toàn bộ hình ảnh và nó hoạt động trên bất kỳ hình ảnh nào .Net có thể tải. Tại sao giải pháp lộn xộn và không hoàn chỉnh này lại có nhiều ủng hộ đến vậy là điều không thể hiểu được.
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;
     }
}

các validateImageDatathiết lập để falsengăn chặn GDI + từ thực hiện phân tích chi phí của các dữ liệu hình ảnh, do đó giảm nghiêm trọng thời gian tải. Câu hỏi này làm sáng tỏ chủ đề hơn.


1
Tôi đã sử dụng giải pháp của bạn làm tài nguyên cuối cùng trộn với giải pháp của ICR ở trên. Đã gặp vấn đề với JPEG và đã giải quyết vấn đề này.
Zorkind

2
Gần đây tôi đã thử điều này trong một dự án mà tôi phải truy vấn kích thước của hơn 2000 hình ảnh (chủ yếu là jpg và png, kích thước rất hỗn hợp) và nó thực sự nhanh hơn nhiều so với cách truyền thống đang sử dụng new Bitmap().
AeonOfTime

1
Câu trả lời tốt nhất. Nhanh chóng, sạch sẽ và hiệu quả.
dynamichael

1
Chức năng này hoàn hảo trên windows. nhưng nó không hoạt động trên linux, nó vẫn sẽ đọc toàn bộ tệp trên linux. (.net core 2.2)
zhengchun

21

Bạn đã thử sử dụng các lớp WPF Imaging chưa? System.Windows.Media.Imaging.BitmapDecoder, Vân vân.?

Tôi tin rằng một số nỗ lực nhằm đảm bảo các codec đó chỉ đọc một tập hợp con của tệp để xác định thông tin tiêu đề. Nó đáng để kiểm tra.


Cảm ơn bạn. Có vẻ hợp lý, nhưng lưu trữ của tôi có .NET 2.
Jan Zich 21/09/08

1
Câu trả lời xuất sắc. Nếu bạn có thể nhận được tham chiếu đến PresentationCore trong dự án của mình, thì đây là cách để thực hiện.
ojrac

Trong các bài kiểm tra đơn vị của tôi, các lớp này không hoạt động tốt hơn GDI ... vẫn yêu cầu ~ 32K để đọc kích thước JPEG.
Nariman

Vì vậy, để có được kích thước hình ảnh của OP, bạn sử dụng BitmapDecoder như thế nào?
Chuck Savage


12

Tôi đã tìm kiếm một thứ tương tự vài tháng trước đó. Tôi muốn đọc loại, phiên bản, chiều cao và chiều rộng của ảnh GIF nhưng không thể tìm thấy bất kỳ thứ gì hữu ích trực tuyến.

May mắn thay trong trường hợp GIF, tất cả thông tin được yêu cầu đều nằm trong 10 byte đầu tiên:

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

PNG phức tạp hơn một chút (chiều rộng và chiều cao mỗi tệp là 4 byte):

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

Như đã đề cập ở trên, wotsit là một trang web tốt cho thông số kỹ thuật chi tiết về định dạng hình ảnh và dữ liệu mặc dù thông số kỹ thuật PNG tại pnglib chi tiết hơn nhiều. Tuy nhiên, tôi nghĩ mục nhập Wikipedia về định dạng PNGGIF là nơi tốt nhất để bắt đầu.

Đây là mã ban đầu của tôi để kiểm tra GIF, tôi cũng đã tổng hợp một số thứ cho 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

Dựa trên các câu trả lời cho đến nay và một số tìm kiếm bổ sung, có vẻ như trong thư viện lớp .NET 2 không có chức năng nào cho nó. Vì vậy, tôi quyết định viết của riêng tôi. Đây là một phiên bản rất thô của nó. Hiện tại, tôi chỉ cần nó cho JPG. Vì vậy, nó hoàn thành câu trả lời được đăng bởi Abbas.

Không có kiểm tra lỗi hoặc bất kỳ xác minh nào khác, nhưng tôi hiện cần nó cho một nhiệm vụ hạn chế và cuối cùng nó có thể dễ dàng thêm vào. Tôi đã thử nghiệm nó trên một số hình ảnh và nó thường không đọc nhiều hơn 6K đó từ một hình ảnh. Tôi đoán nó phụ thuộc vào số lượng dữ liệu 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);
            }
        }

    }
}

Chiều rộng và chiều cao được đảo ngược khi tôi thử điều này.
Jason Sturges

@JasonSturges Bạn có thể cần tính đến thẻ Định hướng Exif.
Andrew Morton

3

Tôi đã làm điều này cho tệp 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);

1

Có, bạn hoàn toàn có thể làm điều này và mã phụ thuộc vào định dạng tệp. Tôi làm việc cho một nhà cung cấp hình ảnh ( Atalasoft ) và sản phẩm của chúng tôi cung cấp GetImageInfo () cho mọi codec tối thiểu để tìm ra kích thước và một số dữ liệu dễ lấy khác.

Nếu bạn muốn tự cuộn, tôi khuyên bạn nên bắt đầu với wotsit.org , trang này có thông số kỹ thuật chi tiết cho hầu hết các định dạng hình ảnh và bạn sẽ thấy cách xác định tệp cũng như nơi có thể tìm thấy thông tin trong đó.

Nếu bạn cảm thấy thoải mái khi làm việc với C, thì bạn cũng có thể sử dụng jpeglib miễn phí để lấy thông tin này. Tôi cá rằng bạn có thể làm điều này với các thư viện .NET, nhưng tôi không biết làm thế nào.


có an toàn không khi cho rằng sử dụng new AtalaImage(filepath).Widthmột cái gì đó tương tự?
drzaus


1
Đầu tiên (AtalaImage) đọc toàn bộ hình ảnh - thứ hai (GetImageInfo) đọc siêu dữ liệu tối thiểu để lấy các phần tử của đối tượng thông tin hình ảnh.
Lou Franco

0

Đã cập nhật câu trả lời của ICR để hỗ trợ các jPegs & WebP tiến bộ :)

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

Nó sẽ phụ thuộc vào định dạng tệp. Thông thường họ sẽ ghi rõ nó trong các byte đầu tiên của tệp. Và, thông thường, việc triển khai đọc hình ảnh tốt sẽ tính đến điều đó. Tuy nhiên, tôi không thể chỉ bạn đến một cho .NET.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.