получение размеров изображения без чтения всего файла

<p>есть ли дешевый способ получить размеры изображения (jpg, png, ...)? предпочтительно, я хотел бы добиться этого, используя только стандартную библиотеку классов (из-за ограничений хостинга). я знаю, что должно быть относительно легко прочитать заголовок изображения и разобрать его сам, но кажется, что нечто подобное уже должно быть. кроме того, я убедился, что следующий фрагмент кода читает все изображение (что мне не нужно):</p><pre><code>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); } } } </code></pre>

<p>лучше всего, как всегда, найти хорошо проверенную библиотеку. тем не менее, вы сказали, что это сложно, поэтому вот какой-то хитрый в значительной степени непроверенный код, который должен работать для значительного числа случаев:</p><pre><code>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); } } } </code></pre><p>надеюсь, код довольно очевиден. чтобы добавить новый формат файла, вы добавляете его в <code>imageformatdecoders</code>, ключом которого является массив "магических битов", которые появляются в начале каждого файла данного формата, а значением является функция, которая извлекает размер из потока. большинство форматов достаточно просты, единственный настоящий вонючий - это jpeg.</p>

<pre><code>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; } } </code></pre><p><code>validateimagedata</code>, установленный в <code>false</code>, не позволяет gdi + выполнять дорогостоящий анализ данных изображения, тем самым значительно сокращая время загрузки. <a href="https://stackoverflow.com/questions/420337/validateimagedata-parameter-and-image-fromstream">этот вопрос</a> проливает больше света на эту тему.</p>

<p>вы пробовали использовать классы wpf imaging? <code>system.windows.media.imaging.bitmapdecoder</code> и т. д.?</p><p>я полагаю, что некоторые усилия были направлены на то, чтобы эти кодеки считывали только подмножество файла, чтобы определить информацию заголовка. это стоит проверить.</p>

<p>я искал что-то подобное несколько месяцев назад. я хотел прочитать тип, версию, высоту и ширину изображения gif, но не смог найти ничего полезного в интернете.</p><p>к счастью, в случае gif вся необходимая информация была в первых 10 байтах:</p><pre><code>type: bytes 0-2 version: bytes 3-5 height: bytes 6-7 width: bytes 8-9 </code></pre><p>png немного сложнее (ширина и высота 4 байта каждая):</p><pre><code>width: bytes 16-19 height: bytes 20-23 </code></pre><p>как упоминалось выше, <a href="http://www.wotsit.org/">wotsit</a> - хороший сайт для подробных спецификаций изображений и форматов данных, хотя спецификации png в <a href="http://www.libpng.org/pub/png/spec/1.1/png-contents.html">pnglib</a> гораздо более подробны. тем не менее, я думаю, что запись в википедии о форматах <a href="https://en.wikipedia.org/wiki/portable\_network\_graphics">png</a> и <a href="http://en.wikipedia.org/wiki/graphics\_interchange\_format">gif</a> - лучшее место для начала.</p><p>вот мой оригинальный код для проверки gif-файлов, я также добавил кое-что для png:</p><pre class="lang-cs prettyprint-override"><code>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); } } </code></pre>

<p>судя по полученным ответам и дополнительному поиску, кажется, что в библиотеке классов .net 2 ее нет. поэтому я решил написать свой. вот очень грубая версия этого. на данный момент мне это нужно было только для jpg. таким образом, он завершает ответ, опубликованный аббасом.</p><p>нет проверки ошибок или какой-либо другой проверки, но в настоящее время она мне нужна для ограниченной задачи, и в конечном итоге ее можно легко добавить. я проверил это на некотором количестве изображений, и он обычно не читает больше 6k из изображения. я думаю, это зависит от количества данных exif.</p><pre><code>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); } } } } </code></pre>

<p>я сделал это для файла png</p><pre><code> 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); </code></pre>

<p>да, вы можете сделать это, и код зависит от формата файла. я работаю на поставщика изображений ( <a href="http://www.atalasoft.com">atalasoft</a> ), и наш продукт предоставляет getimageinfo () для каждого кодека, который делает минимум для определения размеров и некоторых других простых для получения данных.</p><p>если вы хотите свернуть свой собственный, я предлагаю начать с <a href="http://wotsit.org">wotsit.org</a> , который содержит подробные спецификации практически для всех форматов изображений, и вы увидите, как идентифицировать файл, а также информацию о том, где в нем можно найти информацию. найденный.</p><p>если вам удобно работать с c, то для получения этой информации также можно использовать бесплатный jpeglib. могу поспорить, что вы можете сделать это с библиотеками .net, но я не знаю как.</p>