[c#] 전체 파일을 읽지 않고 이미지 크기 얻기

이미지 (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);
        }
    }
}



답변

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

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입니다.


답변

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 +. 이 질문 은 주제에 대해 더 많은 빛을 비 춥니 다.


답변

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

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


답변

나는 몇 달 전에 비슷한 것을 찾고 있었다. 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);
    }
}


답변

지금까지의 답변과 몇 가지 추가 검색에 따르면 .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);
            }
        }

    }
}


답변

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


답변

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

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

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