C# 无需读取整个文件即可获取图像尺寸
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/111345/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Getting image dimensions without reading the entire file
提问by Jan Zich
Is there a cheap way to get the dimensions of an image (jpg, png, ...)? Preferably, I would like to achieve this using only the standard class library (because of hosting restrictions). I know that it should be relatively easy to read the image header and parse it myself, but it seems that something like this should be already there. Also, I've verified that the following piece of code reads the entire image (which I don't want):
有没有一种廉价的方法来获取图像的尺寸(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);
}
}
}
采纳答案by ICR
Your best bet as always is to find a well tested library. However, you said that is difficult, so here is some dodgy largely untested code that should work for a fair number of cases:
与往常一样,您最好的选择是找到一个经过良好测试的库。但是,你说这很困难,所以这里有一些狡猾的基本上未经测试的代码,应该适用于相当多的情况:
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);
}
}
}
Hopefully the code is fairly obvious. To add a new file format you add it to imageFormatDecoders
with the key being an array of the "magic bits" which appear at the beginning of every file of the given format and the value being a function which extracts the size from the stream. Most formats are simple enough, the only real stinker is jpeg.
希望代码是相当明显的。要添加新的文件格式,您将其添加到imageFormatDecoders
其中,键是出现在给定格式的每个文件开头的“魔术位”数组,值是从流中提取大小的函数。大多数格式都足够简单,唯一真正令人讨厌的是 jpeg。
回答by Frank Krueger
Have you tried using the WPF Imaging classes? System.Windows.Media.Imaging.BitmapDecoder
, etc.?
您是否尝试过使用 WPF Imaging 类?System.Windows.Media.Imaging.BitmapDecoder
, 等等。?
I believe some effort was into making sure those codecs only read a subset of the file in order to determine header information. It's worth a check.
我相信一些努力是为了确保这些编解码器只读取文件的一个子集以确定头信息。值得一试。
回答by Kevin Conner
It's going to depend on the file format. Usually they will state it up in the early bytes of the file. And, usually, a good image-reading implementation will take that into account. I can't point you to one for .NET though.
这将取决于文件格式。通常他们会在文件的早期字节中说明它。而且,通常,一个好的图像读取实现会考虑到这一点。不过,我不能为您指出 .NET 的一个。
回答by Lou Franco
Yes, you can absolutely do this and the code depends on the file format. I work for an imaging vendor (Atalasoft), and our product provides a GetImageInfo() for every codec that does the minimum to find out dimensions and some other easy to get data.
是的,您绝对可以这样做,代码取决于文件格式。我为一家成像供应商 ( Atalasoft) 工作,我们的产品为每个编解码器提供了 GetImageInfo(),该编解码器可以最低限度地找出尺寸和其他一些易于获取的数据。
If you want to roll your own, I suggest starting with wotsit.org, which has detailed specs for pretty much all image formats and you will see how to identify the file and also where information in it can be found.
如果你想推出自己的,我建议从wotsit.org开始,它有几乎所有图像格式的详细规范,你将看到如何识别文件以及在哪里可以找到其中的信息。
If you are comfortable working with C, then the free jpeglib can be used to get this information too. I would bet that you can do this with .NET libraries, but I don't know how.
如果您习惯于使用 C,那么也可以使用免费的 jpeglib 来获取此信息。我敢打赌你可以用 .NET 库来做到这一点,但我不知道怎么做。
回答by Abbas
I was looking for something similar a few months earlier. I wanted to read the type, version, height and width of a GIF image but couldn't find anything useful online.
几个月前我一直在寻找类似的东西。我想阅读 GIF 图像的类型、版本、高度和宽度,但在网上找不到任何有用的信息。
Fortunately in case of GIF, all the required information was in the first 10 bytes:
幸运的是,对于 GIF,所有必需的信息都在前 10 个字节中:
Type: Bytes 0-2
Version: Bytes 3-5
Height: Bytes 6-7
Width: Bytes 8-9
PNG are slightly more complex (width and height are 4-bytes each):
PNG 稍微复杂一些(宽度和高度各为 4 个字节):
Width: Bytes 16-19
Height: Bytes 20-23
As mentioned above, wotsitis a good site for detailed specs on image and data formats though the PNG specs at pnglibare much more detailed. However, I think the Wikipedia entry on PNGand GIFformats is the best place to start.
如上所述,wotsit是一个很好的网站,提供有关图像和数据格式的详细规范,尽管pnglib的 PNG 规范要详细得多。但是,我认为PNG和GIF格式的维基百科条目是最好的起点。
Here's my original code for checking GIFs, I have also slapped together something for PNGs:
这是我检查 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);
}
}
回答by Jan Zich
Based on the answers so far and some additional searching, it seems that in the .NET 2 class library there is no functionality for it. So I decided to write my own. Here is a very rough version of it. At the moment, I needed it only for JPG's. So it completes the answer posted by Abbas.
根据到目前为止的答案和一些额外的搜索,似乎在 .NET 2 类库中没有它的功能。所以我决定自己写。这是它的一个非常粗略的版本。目前,我只需要为 JPG 使用它。所以它完成了阿巴斯发布的答案。
There is no error checking or any other verification, but I currently need it for a limited task, and it can be eventually easily added. I tested it on some number of images, and it usually does not read more that 6K from an image. I guess it depends on the amount of the EXIF data.
没有错误检查或任何其他验证,但我目前需要它来执行有限的任务,并且最终可以轻松添加它。我在一定数量的图像上对其进行了测试,它通常不会从图像中读取超过 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);
}
}
}
}
回答by Koray
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;
}
}
the validateImageData
set to false
prevents GDI+ from performing costly analysis of the image data, thus severely decreasing load time. This questionsheds more light on the subject.
该validateImageData
设置false
可防止 GDI+ 对图像数据执行昂贵的分析,从而严重减少加载时间。 这个问题更能说明这个问题。
回答by Danny D
I did this for PNG file
我为 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);
回答by bang
Updated ICR's answer to support progressive jPegs & WebP as well :)
更新了 ICR 的答案以支持渐进式 jPegs 和 WebP :)
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);
}
}