Сайт для программистов

вторник, 9 февраля 2010 г.

Получить информацию о BMP-файле

Если вам приходится работать с графическими файлами, то, возможно, у вас появится задача получить дополнительную информацию - узнать размеры файла, глубину цвета и т.п. Безусловно, вы можете загрузить картинку из файла на форму или подходящий элемент управления (PictureBox) и получить требуемую информацию. Но, предположим, ваш проект предназначен только для сбора информации о файлах. В этом случае загружать 10-мегапиксельный рисунок в форму неоправданно - получается слишком большой расход памяти. Есть другой способ получения нужной информации. О нем и пойдет речь сегодня.

Графические файлы являются бинарными файлами с определенной структурой. Так вот, в файле уже содержатся необходимые нам данные. Наша задача - уметь извлечь данную информацию. Не будем углубляться в технические детали форматов графических файлов. Достаточно знать, что, например, ширина картинки записана всегда в строго определенном месте от начала файла. Поэтому, нам нужно только открыть файл для считывания байтов, отсчитать нужное количество байт и получить необходимое число. Кстати, по такому принципу устроены не только графические файлы, но и многие другие файлы, например, музыкальные.

Но, вернемся к файлам BMP. В начале каждого файла в формате BMP идет так называемый заголовок файла. В документации он описывается структурой BITMAPFILEHEADER, которая имеет общую длину 14 байтов: три UInt16 и два UInt32. Нас не интересует данный заголовок, поэтому вы его должны просто проигнорировать. Таким образом в коде нам нужно открыть файл и пропустить 14 байт:

reader.ReadBytes(14);

Обратите внимание, что функцию sizeof в данном случае использовать нельзя. Функция sizeof работает только с неуправляемыми ресурсами. После структуры BITMAPFILEHEADER идет структура BITMAPINFOHEADER. Именно, в этой структуре хранится необходимая информация, которую необходимо извлечь (кстати, описание структуры можно найти в справочнике по функциям Windows API для Visual Basic). Вот как будет выглядеть код извлечения информации из файла:

private void ShowBMPInfo(String file)
{
FileStream fs = new FileStream(file, FileMode.Open);
BinaryReader reader = new BinaryReader(fs);

// Пропускаем заголовок файла (14 байтов)
reader.ReadBytes(14);

// Пропускаем размер структуры BITMAPINFOHEADER
reader.ReadUInt32();

// Получаем ширину и высоту
int width = reader.ReadInt32();
int height = reader.ReadInt32();

// опять пропускаем часть байтов
reader.ReadInt16();

// Получаем глубину цвета (битов на пиксел)
Int16 bitsPerPixel = reader.ReadInt16();

// Освобождаем ресурсы
reader.Close();
fs.Close();

// Выводим информацию
MessageBox.Show(width + " x " + height + " x " + bitsPerPixel);
}

private void button1_Click(object sender, EventArgs e)
{
ShowBMPInfo(textBox1.Text); // подставляем путь к файлу
}

Тут есть одна тонкость. Пропускаемые в коде байты на самом деле все равно считываются. Чтобы действительно перепрыгнуть через заданное число байтов, вы должны использовать методы и свойства объекта потока (stream object). Например, получить текущий поток через свойство BaseStream и пропустить первые 14 байтов можно так:


reader.BaseStream.Seek(14, SeekOrigin.Begin);

Кроме того, можно передвинуть внутренний указатель на заданное смещение от конца потока или от текущей позиции. Вот как пропустить короткое целое (2 байта):


reader.BaseStream.Seek(2, SeekOrigin.Current);

С учетом этих особенностей я написал второй вариант извлечения данных из файла.


private void ShowBMPInfo2(String file)
{
FileStream fs = new FileStream(file, FileMode.Open);
BinaryReader reader = new BinaryReader(fs);

// Пропускаем заголовок файла (14 байтов) без считывания
reader.BaseStream.Seek(14, SeekOrigin.Begin);

// Пропускаем размер структуры без считывания
reader.BaseStream.Seek(4, SeekOrigin.Current);

// Получаем ширину и высоту
int width = reader.ReadInt32();
int height = reader.ReadInt32();

// Пропускаем
reader.ReadInt16();

// Получаем глубину цвета (битов на пиксел)
Int16 bitsPerPixel = reader.ReadInt16();

reader.Close();
fs.Close();

// Выводим информацию
MessageBox.Show(width + " x " + height + " x " + bitsPerPixel * planes);
}

private void button2_Click(object sender, EventArgs e)
{
ShowBMPInfo2(textBox1.Text);
}

Толчком к написаню данного топика послужила статья из журнала MSDN за август 2002 года. Там же вы можете найти код для Visual Basic.NET.

Данный совет также будет помещен в раздел Графика на моем сайте.

Удачного вам программирования!

Комментариев нет: