Есть ли дешевый способ получить размеры изображения (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);
}
}
}
Если у вас есть доступ к пространству имен System.Windows.Media.Imaging (в WPF), см. Этот вопрос SO: stackoverflow.com/questions/784734/…





Вы пробовали использовать классы WPF Imaging? System.Windows.Media.Imaging.BitmapDecoder и др.?
Я считаю, что некоторые усилия были предприняты для того, чтобы эти кодеки считывали только подмножество файла, чтобы определить информацию заголовка. Стоит проверить.
Спасибо. Вроде разумно, но на моем хостинге .NET 2.
Отличный ответ. Если вы можете получить ссылку на PresentationCore в своем проекте, это правильный путь.
В моих модульных тестах эти классы работают не лучше, чем GDI ... по-прежнему требуется ~ 32 КБ для чтения размеров JPEG.
Итак, чтобы получить размеры изображения OP, как использовать BitmapDecoder?
См. Этот вопрос SO: stackoverflow.com/questions/784734/…
Это будет зависеть от формата файла. Обычно они указывают это в первых байтах файла. И, как правило, хорошая реализация чтения изображений учитывает это. Я не могу указать вам на один для .NET.
Да, вы можете это сделать, и код зависит от формата файла. Я работаю на поставщика изображений (Atalasoft), и наш продукт предоставляет GetImageInfo () для каждого кодека, который делает минимум для определения размеров и некоторых других простых для получения данных.
Если вы хотите создать свой собственный, я предлагаю начать с wotsit.org, в котором есть подробные спецификации практически для всех форматов изображений, и вы увидите, как идентифицировать файл, а также где можно найти в нем информацию.
Если вам комфортно работать с C, то для получения этой информации также можно использовать бесплатный jpeglib. Готов поспорить, что вы можете сделать это с помощью .NET-библиотек, но я не знаю, как это сделать.
можно ли предположить, что использование new AtalaImage(filepath).Width делает что-то подобное?
или просто Atalasoft.Imaging.Codec.RegisteredDecoders.GetImageInfo ( fullPath ) .Size
Первый (AtalaImage) считывает все изображение - второй (GetImageInfo) считывает минимальные метаданные для получения элементов информационного объекта изображения.
Несколько месяцев назад я искал нечто подобное. Я хотел узнать тип, версию, высоту и ширину изображения в формате 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 гораздо более детализированы. Однако я думаю, что запись в Википедии о форматах PNG и Гифка - лучшее место для начала.
Вот мой исходный код для проверки 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.
Согласен, JPEG - отстой. Кстати, примечание для людей, которые хотят использовать этот код в будущем: это действительно непроверено. Я прошел через это с помощью тонкой гребенки, и вот что я обнаружил: формат BMP имеет еще один (древний) вариант заголовка, размер которого составляет 16 бит; плюс высота может быть отрицательной (тогда опустите знак). Что касается JPEG - 0xC0 - не единственный заголовок. В основном все от 0xC0 до 0xCF, кроме 0xC4 и 0xCC, являются допустимыми заголовками (вы можете легко получить их в чересстрочных файлах JPG). И, чтобы было веселее, высота может быть равна 0 и указана позже в блоке 0xDC. См. w3.org/Graphics/JPEG/itu-t81.pdf
Изменен метод DecodeJfif выше, чтобы расширить исходную (marker == 0xC0) проверку, чтобы она также принимала 0xC1 и 0xC2. Эти другие заголовки начала кадра SOF1 и SOF2 кодируют ширину / высоту в одних и тех же позициях байтов. SOF2 довольно распространен.
Стандартное предупреждение: вы никогда не должны писать throw e;, а просто throw;. В комментариях вашего XML-документа ко второму GetDimensions также отображается path вместо binaryReader.
@RyanBarton Не могли бы вы опубликовать изменения кода? Это действительно очень поможет мне с подобной проблемой. Спасибо.
@SteveJohnson: отредактированный код для включения проверок SOF1 / SOF2 в DecodeJfif.
Кроме того, похоже, этот код не принимает файлы JPEG, закодированные в формате EXIF / TIFF, который выводится многими цифровыми камерами. Он поддерживает только JFIF.
System.Drawing.Image.FromStream (stream, false, false) предоставит вам размеры без загрузки всего изображения, и он работает с любым изображением, которое может загрузить .Net. Непонятно, почему это беспорядочное и неполное решение вызвало так много положительных отзывов.
@dynamichael могут возникнуть ситуации, когда у вас нет доступа к этой библиотеке, поэтому эти решения необходимы.
@ MattyMatt2 Из OP: «Лучше всего я хотел бы добиться этого, используя только стандартную библиотеку классов»
@dynamichael System.Drawing в наши дни является стандартной библиотекой нет; он основан на GDI +, и существует множество платформ C#, на которых он недоступен.
Кстати, BinaryReader специально предназначен для чтения с прямым порядком байтов. Вспомогательные функции не нужны. Фактически, для части PNG вам нужно читать с прямым порядком байтов; png все внутреннее устройство с прямым порядком байтов.
На основании полученных ответов и некоторого дополнительного поиска кажется, что в библиотеке классов .NET 2 для нее нет функциональности. Поэтому я решил написать свое. Вот его очень грубая версия. На данный момент мне он нужен только для JPG. Таким образом, он завершает ответ, опубликованный Аббасом.
Здесь нет проверки ошибок или какой-либо другой проверки, но в настоящее время она мне нужна для ограниченной задачи, и ее можно легко добавить. Я тестировал его на некотором количестве изображений, и обычно он не читает больше 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.
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 + дорогостоящего анализа данных изображения, тем самым значительно сокращая время загрузки. Этот вопрос проливает больше света на предмет.
Я использовал ваше решение в качестве последнего ресурса, смешанного с решением ICR, приведенным выше. Были проблемы с JPEG, и решил с этим.
Недавно я попробовал это в проекте, где мне нужно было запросить размер более 2000 изображений (в основном jpg и png, очень смешанные размеры), и это действительно было намного быстрее, чем традиционный способ с использованием new Bitmap().
Лучший ответ. Быстро, чисто и эффективно.
Эта функция идеально подходит для окон. но он не работает в Linux, он все равно будет читать весь файл в Linux. (.net ядро 2.2)
Я сделал это для файла 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);
}
}
Спасибо, что запустили webp. DecodeWebP работает только с изображениями Webp Lossy - developers.google.com/speed/webp/gallery1
Внутреннее устройство png все с прямым порядком байтов (я полагаю, что и gif). А BinaryReader в любом случае всегда читает прямой порядок байтов, независимо от порядка байтов в системе, поэтому существующие вспомогательные функции бесполезны.
Было бы полезно, если бы вы были более конкретны в самом вопросе. Теги сказали мне .net и C#, и вам нужна стандартная библиотека, но какие эти ограничения хостинга вы упоминаете?