전체 파일을 읽지 않고 이미지 크기 얻기


104

이미지 (jpg, png, ...)의 크기를 얻을 수있는 저렴한 방법이 있습니까? 바람직하게는 표준 클래스 라이브러리 만 사용하여이를 달성하고 싶습니다 (호스팅 제한 때문에). 이미지 헤더를 읽고 직접 파싱하는 것이 상대적으로 쉬워야한다는 것을 알고 있지만 이와 같은 것이 이미 존재해야하는 것 같습니다. 또한 다음 코드가 전체 이미지를 읽는다는 것을 확인했습니다 (원하지 않음).

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

적절한 질문에 좀 더 구체적이면 도움이 될 것입니다. 태그는 나에게 .net 및 C #을 알려주고 표준 라이브러리를 원하지만 이러한 호스팅 제한 사항은 무엇입니까?
wnoise

System.Windows.Media.Imaging 네임 스페이스 (WPF)에 대한 액세스 권한이있는 경우 다음 SO 질문을 참조하십시오. stackoverflow.com/questions/784734/…
Charlie

답변:


106

항상 그렇듯이 가장 좋은 방법은 잘 테스트 된 라이브러리를 찾는 것입니다. 그러나 당신은 그것이 어렵다고 말 했으므로 여기에 상당히 많은 경우에 작동해야하는 테스트되지 않은 코드가 있습니다.

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

코드가 상당히 분명하기를 바랍니다. 새로운 파일 형식을 추가 imageFormatDecoders하려면 주어진 형식의 모든 파일의 시작 부분에 나타나는 "매직 비트"의 배열 인 키와 스트림에서 크기를 추출하는 함수 인 값을 사용하여 추가합니다. 대부분의 형식은 충분히 간단하며 유일한 진짜 악취는 jpeg입니다.


6
동의합니다. JPEG는 형편 없습니다. Btw-미래에이 코드를 사용하려는 사람들을위한 메모 : 이것은 실제로 테스트되지 않았습니다. 나는 미세한 빗으로 그것을 살펴 보았고, 여기에 내가 찾은 것이있다 : BMP 형식은 크기가 16 비트 인 또 다른 (고대) 헤더 변형을 가지고있다. 더하기 높이는 음수 일 수 있습니다 (그런 다음 기호를 삭제). JPEG의 경우-0xC0이 유일한 헤더는 아닙니다. 기본적으로 0xC4 및 0xCC를 제외한 모든 0xC0 ~ 0xCF는 유효한 헤더입니다 (인터레이스 JPG로 쉽게 가져올 수 있음). 그리고 더 재미있게 만들기 위해 높이는 0이고 나중에 0xDC 블록에 지정할 수 있습니다. 참조 w3.org/Graphics/JPEG/itu-t81.pdf
Vilx-

위의 DecodeJfif 메서드를 조정하여 원본 (마커 == 0xC0) 검사를 확장하여 0xC1 및 0xC2도 허용합니다. 이러한 다른 프레임 시작 헤더 SOF1 및 SOF2는 동일한 바이트 위치에서 너비 / 높이를 인코딩합니다. SOF2는 상당히 일반적입니다.
Ryan Barton

4
표준 경고 : 작성해서는 안되며 throw e;단순히 throw;대신 작성하십시오. 두 번째에 귀하의 XML 문서 주석 GetDimensions도 보여 path대신binaryReader
Eregrith

1
또한이 코드는 많은 디지털 카메라에서 출력되는 EXIF ​​/ TIFF 형식으로 인코딩 된 JPEG를 허용하지 않는 것 같습니다. JFIF 만 지원합니다.
cwills

2
System.Drawing.Image.FromStream (stream, false, false)은 전체 이미지를로드하지 않고 치수를 제공하며 .Net이로드 할 수있는 모든 이미지에서 작동합니다. 이 지저분하고 불완전한 솔루션에 많은 찬성표가있는 이유는 이해할 수 없습니다.
dynamichael 2018

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

validateImageData으로 설정 false하므로 심하게 로딩 시간을 감소시키는 상기 화상 데이터의 비용 분석을 수행 방지 GDI +. 이 질문 은 주제에 대해 더 많은 빛을 비 춥니 다.


1
위의 ICR 솔루션과 혼합 된 마지막 리소스로 귀하의 솔루션을 사용했습니다. JPEG에 문제가 있었고 이것으로 해결되었습니다.
Zorkind

2
저는 최근에 2000 개 이상의 이미지 (대부분 jpg와 png, 매우 혼합 된 크기)의 크기를 쿼리해야하는 프로젝트에서 이것을 시도했으며 실제로 .NET을 사용하는 기존 방식보다 훨씬 빠릅니다 new Bitmap().
AeonOfTime

1
최고의 답변입니다. 빠르고 깨끗하며 효과적입니다.
dynamichael 2018

1
이 기능은 창문에서 완벽합니다. 그러나 그것은 리눅스에서 작동하지 않지만 여전히 리눅스에서 전체 파일을 읽습니다. (.net 코어 2.2)
정춘

21

WPF 이미징 클래스를 사용해 보셨습니까? System.Windows.Media.Imaging.BitmapDecoder등?

헤더 정보를 확인하기 위해 코덱이 파일의 하위 집합 만 읽도록하는 데 약간의 노력이 있었다고 생각합니다. 확인해 볼 가치가 있습니다.


감사합니다. 합리적으로 보이지만 내 호스팅에는 .NET 2가 있습니다.
Jan Zich

1
훌륭한 대답입니다. 프로젝트에서 PresentationCore에 대한 참조를 얻을 수 있다면 이것이 갈 길입니다.
ojrac

내 단위 테스트에서 이러한 클래스는 GDI보다 더 나은 성능을 발휘하지 못합니다. JPEG 크기를 읽으려면 여전히 ~ 32K가 필요합니다.
Nariman 2012

따라서 OP의 이미지 크기를 얻으려면 BitmapDecoder를 어떻게 사용합니까?
척 야만인

1
이 SO 질문을 참조하십시오 : stackoverflow.com/questions/784734/…
Charlie

12

나는 몇 달 전에 비슷한 것을 찾고 있었다. GIF 이미지의 유형, 버전, 높이 및 너비를 읽고 싶었지만 온라인에서 유용한 정보를 찾을 수 없었습니다.

다행히 GIF의 경우 필요한 모든 정보가 처음 10 바이트에있었습니다.

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

PNG는 약간 더 복잡합니다 (너비와 높이는 각각 4 바이트).

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

위에서 언급 한 바와 같이, wotsit는 에서 PNG 사양 불구하고 이미지 데이터 형식에 대한 자세한 사양에 대한 좋은 사이트입니다 pnglib이 훨씬 더 자세히 설명되어 있습니다. 그러나 PNGGIF 형식 에 대한 Wikipedia 항목 이 시작하기에 가장 좋은 곳 이라고 생각합니다 .

다음은 GIF를 확인하기위한 원래 코드입니다. 또한 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

지금까지의 답변과 몇 가지 추가 검색에 따르면 .NET 2 클래스 라이브러리에는 기능이없는 것 같습니다. 그래서 나는 직접 쓰기로 결정했습니다. 여기에 아주 대략적인 버전이 있습니다. 현재로서는 JPG에만 필요했습니다. 그래서 Abbas가 게시 한 답변을 완성합니다.

오류 확인이나 다른 확인은 없지만 현재 제한된 작업에 필요하며 결국 쉽게 추가 할 수 있습니다. 몇 개의 이미지에서 테스트했는데 일반적으로 이미지에서 6K 이상을 읽지 않습니다. 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);
            }
        }

    }
}

이것을 시도하면 너비와 높이가 반전됩니다.
제이슨 스터지스

@JasonSturges Exif Orientation 태그를 고려해야 할 수도 있습니다.
Andrew Morton

3

나는 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

예, 절대적으로 할 수 있으며 코드는 파일 형식에 따라 다릅니다. 저는 이미징 공급 업체 ( Atalasoft )에서 일하고 있으며, 우리 제품은 최소한의 크기와 다른 데이터를 쉽게 얻을 수있는 모든 코덱에 대해 GetImageInfo ()를 제공합니다.

직접 롤링하려면 거의 모든 이미지 형식에 대한 세부 사양이 있는 wotsit.org로 시작하는 것이 좋으며 파일을 식별하는 방법과 파일의 정보를 찾을 수있는 위치를 볼 수 있습니다.

C 작업에 익숙하다면 무료 jpeglib를 사용하여이 정보를 얻을 수도 있습니다. .NET 라이브러리를 사용하여이 작업을 수행 할 수 있다고 장담하지만 방법을 모르겠습니다.


사용 new AtalaImage(filepath).Width하는 것이 비슷한 일 을한다고 가정하는 것이 안전 합니까?
drzaus


1
첫 번째 (AtalaImage)는 전체 이미지를 읽고 두 번째 (GetImageInfo)는 최소 메타 데이터를 읽어 이미지 정보 개체의 요소를 가져옵니다.
Lou Franco

0

프로그레시브 jPeg 및 WebP를 지원하기 위해 ICR의 답변 업데이트 :)

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

파일 형식에 따라 다릅니다. 일반적으로 파일의 초기 바이트에 설명합니다. 그리고 일반적으로 좋은 이미지 읽기 구현은이를 고려합니다. 그래도 .NET 용으로 지적 할 수는 없습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.