在不读取整个文件的情况下获取图像尺寸

有没有一种便宜的方法来获得一个图像的尺寸(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);
}
}
}
88651 次浏览

你有没有试过使用 WPF 成像类? System.Windows.Media.Imaging.BitmapDecoder等等?

我相信一些努力是为了确保这些编解码器只读取文件的一个子集,以便确定头信息。值得一查。

这取决于文件格式。通常他们会在文件的前面几个字节中表明。而且,通常,一个好的图像阅读实现会考虑到这一点。我不能告诉你。但是。

是的,您完全可以这样做,代码取决于文件格式。我为一家成像供应商(Atalasoft)工作,我们的产品为每一个编解码器提供了一个 GetImageInfo () ,这些编解码器尽量少地查找尺寸和其他一些容易获得的数据。

如果您想自己卷,我建议从 Wotsit.org开始,它有几乎所有图像格式的详细规格,您将看到如何识别文件,以及在哪里可以找到其中的信息。

如果您喜欢使用 C 语言,那么也可以使用免费的 jpeglib 来获取这些信息。我敢打赌,你可以做到这一点。NET 库,但我不知道如何。

几个月前我也在找类似的东西。我想阅读的类型,版本,高度和宽度的 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

如上所述,等等是一个很好的网站详细规格的图像和数据格式,虽然在 说得好的 PNG 规格更详细。然而,我认为维基百科关于 巴布亚新几内亚GIF格式的条目是最好的起点。

这是我检查 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);
}
}

一如既往,您最好的选择是找到一个测试良好的库。然而,您说过这很困难,所以这里有一些不可靠的、基本上没有经过测试的代码,它们应该可以在相当多的情况下工作:

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。

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.

没有错误检查或任何其他验证,但我目前需要一个有限的任务,它可以很容易地最终添加。我测试了一些图像,它通常不会读取超过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);
}
}


}
}
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 + 对图像数据进行昂贵的分析,从而大大减少加载时间。这个问题为这个问题提供了更多的线索。

我这样做的 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);

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


}