Формат и чтение BMP

Автор: | 05.02.2018

Формат монохромного BMP файла
VLISP программа считывания данных из BMP-файла (монохромного)
Формат BMP (24-bit Bitmap)
Загрузка и чтение BMP (24-bit Bitmap) на VB.NET
C++ программа считывания данных из BMP (24-bit Bitmap)
Формат Bmp (256-цветной)

Формат монохромного BMP файла

Создайте в графическом редакторе PAINT растровое изображение размером 50*35 пикселов. Выберите максимальный масштаб изображения и включите сетку. Сохраните рисунок в формате BMP как монохромный (файл 50_35.bmp).

Откройте листинг файла 50_35.bmp в приложении Commander (TC, VC или WC) для просмотра (клавиша F3) в шестнадцатеричном (HEX) представлении.

 

Сопоставим картинку растрового изображения и  HEX коды в листинге файла 50_35.bmp.

Коды с 19-го по 23-й (32 00 00 00) определяют размер растра (число пикселов) по горизонтали. Коды с 24-го по 27-й (23 00 00 00) определяют размер растра по вертикали. Чтобы убедиться в этом воспользуйтесь калькулятором. В рассматриваемом примере размер растра – 50 * 35. HEX коду 32  соответствует десятичное (DEC) число 50, HEX коду 23 – DEC число 35.

 

На размера растра отводится 4 байта (HEX кода). Одним байтом определяется размер от 0 до 255. При превышении 255 (код FF) будет задействован следующий байт. Например, DEC числу 256 соответствует HEX код 100. Размер растра 256 будет представлен в листинге файла (00 01 00 00).

 

Коды символов, начиная с 63-го и до конца файла, содержат последовательность данных о цвете точек растра – в порядке слева направо вдоль каждой ряда и снизу вверх по рядам. Один байт описывает 8 точек, белая точка в BIN коде описывается единицей, черная – нулем.

Сколько байт необходимо для описания 52 точек в одном ряду? Не менее 7 (7*8=56). Но используется 8 (8*8=64), соблюдается кратность 4 байтам (4*8=32). Обратите внимание в листинге файла на HEX коды в конце описания каждого ряда повторяются байты FF FF C0 00. Эти байты описывают последние 32 точки из 64-х. Из них 12 несуществующих точек (64-52=12) описаны нулями.

Точки какого ряда описаны кодом FC 1F? Отсчитываем ряды по (по 8 байт в каждом), можно также ориентироваться на код (C0 00) в конце каждого ряда. Получаем 11-й ряд. В нем находятся 5 точек. Проверяем результат с помощью калькулятора.

 

VLISP программа считывания данных из BMP-файла (монохромного)

Программа последовательно считывает из BMP-файла HEX-коды. По этим данным определяется порядковый номер каждой черной точки в ряду, начиная слева. Порядковые номера черных точек каждого ряда заносятся в список, который после окончании чтения ряда записывается в файл Result.txt.

Файл Rbmp.lsp

(defun C:bmp(/ xsize ysize x n fi fo lst mask)
(setq fi (open " E:/tmp/50_35.bmp" "r"))
(setq fo (open " E:/tmp/result.txt" "w"))
(repeat 18 (c))
(setq xsize (r))
(setq ysize (r))
(repeat 36 (c))
(write-line "Size" fo)
(write-line (itoa xsize) fo)
(write-line (itoa ysize) fo)
(repeat ysize
(m)
(setq x 1)
(setq lst nil)
(repeat xsize
(if (= (logand n mask) 0) (setq lst (cons x lst)))
; сдвиг одного бита вправо (01000...0)
(setq mask (lsh mask -1))
(if (= mask 0) (m)) ; подготовка новой маски
(setq x (1+ x)))
(princ (reverse lst) fo) (write-line "" fo)
)
(close fi)
(close fo)
)

; Пользовательские функции
; считывание HEX-символа
(defun c() (read-char fi))
; подготовка маски - 32 bit-символа (10000...0)
; и определение числа из последовательности 4-х HEX-символов
(defun m() (setq mask (lsh 1 31))
(setq n (+ (lsh (c) 24) (lsh (c) 16) (lsh (c) 8) (c))))
; определяется число из обратной
;последовательности 4-х HEX-символов
(defun r ()(setq n (+ (c)(lsh (c) 8) (lsh (c) 16)(lsh (c) 24))))

Информация, записанная в файл Result.txt:

Size
50
35
nil
nil
nil
nil
nil
nil
nil
nil
nil
nil
(31 32 33 34 35)
(30 31 32 33 34)
(29 30 31 32 33 34)
(28 29 30 31 32 33 34 35)
(26 27 28 29 30 31 32 33 34)
(26 27 28 29 30 31 32 33)
(26 27 28 29 30 31 32)
(25 26 27 28 29 30)
(24 25 26 27 28 29)
(24 25 26 27 28 29)
(22 23 24 25 26 27 28)
(21 22 23 24 25 26 27)
(21 22 23 24 25 26)
(21 22 23 24 25)
(20 21 22 23 24)
(18 19 20 21 22 23)
nil
nil
nil
nil
nil
nil
nil
nil
nil

Описание  программы

Из BMP-файла  последовательно считываются байты с помощью функции read-char. Данные, которые будут использоваться в программе, запоминаются в переменных, остальные пропускаются.

Размер растра (xsize и ysize) определяется парой из 4-х байт. Как объединить эти 4 байта в одно число? Рассмотрим более простую задачу. Как из 4-х цифр «5» «8» «9» и «1» сформировать число 1985? Для этого каждую цифру сдвигаем влево на соответствующее количество разрядов (добавляем нули слева), а результат суммируем.

Аналогично решается задача формирования 4-х байтного числа. В пользовательской функции (r) из 4-х байтов сложением формируется одно число. Каждый байт перед сложением сдвигается на соответствующее количество разрядов (1, 2 и 3 байта). Сдвиг выполняется функцией Lsh, параметры сдвига указывается в битах (8, 16 и 24). Функция (r) возвращает число в 10-тичном представлении. Результат запоминается в переменных (xsize и ysize).

Далее считываются и обрабатываются точки, на предмет выявления их цвета. Используется внешний цикл по ysize и внутренний цикл по xsize.

Учитывая, что число байтов, которые описывают один ряд точек, кратно 4 объединим их по аналогии с предыдущей задачей. Только в этом случае из 4-х цифр «5» «8» «9» и «1» будем формировать число 5891:

Эту задачу в программе решает пользовательская функция (r). Она объединяет 4-х байта в 32 разрядное число, которое сохраняется в переменной n.  В функции (r) также создается 32 разрядное число (сохраняется в переменной mask), первый бит которого –  1(единица), остальные –  0 (ноль). Начальное значение «маски» формируется путем побитового сдвига  единицы влево на 31 разряд (lsh 1 31):

Отметим, что любое число независимо от его представления (HEX, DEC, BIN) остается одним и тем же. А функции обрабатывают его по-разному. Например, функция read-char работает с байтами, а Lsh с битами.

После тестирования первого пиксела, единица в маске сдвигается вправо (lsh mask -1):

Тестирование каждой точки, которая описывается в переменной n, анализируется путем применения побитового умножения  c значением переменной mask(if (= (logand n mask) 0) (setq lst (cons x lst))):

В верхнем примере проверялась крайняя 6-я точка сформированного числа, в нижнем примере – 19-я точка. Если результат побитового умножения ноль, то проверяемая точка – черная. Порядковый номер точки в ряду сохраняется в переменой x. Каждая черная точка заносится в начало списка (setq lst (cons x lst)). По окончании анализа ряда список записывается наоборот в файле результата result.txt (princ (reverse lst) fo).

Маска и число обновляются после 32 сдвигов (if (= mask 0) (m)), а также, в случае, если заканчивается внутренний цикл (repeat xsize…). При этом, лишние пикселы, которые находятся за пределами растра (64-52=12), автоматически отсекаются, поскольку значения переменной n обновляются, как и значение переменной mask.

Формат BMP (24-bit Bitmap)

На рисунке приведен пример простого изображения (размер 34 *10 точек)  из 3-х красных точек в левом нижнем углу, остальные точки белые.

Раскрываем листинг файла и, сопоставляя с изображением, определяем, как описываются данные в нем:

Файл начинается с символов “BM”, указывающих на формат файла.

С 19 позиции 4 байта (22 00 00 00) указывают размер файла по X (количество точек в строке 34), с 23-й позиции – 4 байта (0A 00 00 00) указывают размер файла по Y (количество рядов 10).

Начиная с 55-го байта и до конца файла содержатся данные о цвете точек растра – в порядке слева направо вдоль каждого ряда и снизу вверх по рядам. Одна точка описывается тремя байтами, каждый из которых представляет синюю (Blue), зеленую (Green) и красную (Red) составляющие цвета. В рассматриваемом примере первая точка (красного цвета) описана байтами (00 00 FF) вторая (белого цвета) – (FF FF FF).

Каждый ряд точек описывается количеством байт, кратным 4. Ряд из 34 точек должен описываться 34*3 + 2 =104 байтами. Два байта (00 00)  добавляется в конце описания каждого ряда для обеспечения кратности 4. Если бы ряд состоял из 33 точек, то в конце каждого ряда добавилось по 1 байту для обеспечения кратности 4 (33*3  + 1 =100).

Загрузка и чтение BMP (24-bit Bitmap) на VB.NET

Ниже приводится модуль программы, который используется в приложении для идентификации продуктов (фруктов и овощей) на основе данных от видеокамеры. Модуль обеспечивает загрузку картинки и считывания точек (по 3 байта на каждую точку) из файла.

 

Public Class frmIdntf

Dim NameFile As String
Dim FNum1, Xsize, Ysize, Dp, IK, IR, IG, IB, I, J As Integer

Private Sub cmdLoad_Click(sender As Object, e As EventArgs) Handles cmdLoad.Click
With OpenFileDialog
On Error GoTo ErrorLabel
.ShowDialog()
NameFile = .FileName
imgPrdct.ImageLocation = NameFile
End With
ErrorLabel:
Exit Sub
End Sub

Private Sub cmdRun_Click(sender As Object, e As EventArgs) Handles cmdRun.Click
FNum1 = FreeFile()
FileOpen(FNum1, NameFile, OpenMode.Binary)
Xsize = f2(19)
Ysize = f2(23)
Dp = f3(Xsize)         ' количество лишних байтов в ряду
I = 54
For Y = 1 To Ysize
For X = 1 To Xsize
I = I + 1
IB = F1(I)
I = I + 1
IG = F1(I)
I = I + 1
IR = F1(I)
Next X
I = I + Dp     ' сдвиг на количество лишних байтов в ряду
Next Y
FileClose(FNum1)
End Sub

Private Function F1(I) As Byte
FileGet(FNum1, F1, I)
End Function

Private Function f2(I)
Dim N1, N2, N3, N4 As Long
N1 = F1(I)
N2 = F1(I + 1)
N2 = N2 * 256       'сдвиг на байт
N3 = F1(I + 2)
N3 = N3 * 65536     'сдвиг на 2 байта   (256 в квадрате)
N4 = F1(I + 3)
N4 = N4 * 16777216   'сдвиг на 3 байта  (256 в кубе)
f2 = N1 + N2 + N3 + N4
End Function

Private Function f3(Xsize)
Dim N1, N2, N3 As Long
N1 = Xsize * 3
N2 = N1 \ 4
N3 = N2 * 4
If N1 > N3 Then f3 = (N2 + 1) * 4 - N1 Else : f3 = 0
End Function

End Class

С++ программа считывания данных из BMP (24-bit Bitmap)

Ниже приводится модуль программы, который используется в приложении 3D-реконструкция по 2-м изображениям.  Модуль обеспечивает загрузку данных из BMP файлов в динамически выделяемую оперативную память.

RGBQUAD ** MatrixBMP(const char *fname, int &mx, int &my)
{
FILE * pFile = fopen(fname, "rb");

// считываем заголовок файла
BITMAPFILEHEADER header;
header.bfType = read_u16(pFile);
header.bfSize = read_u32(pFile);
header.bfReserved1 = read_u16(pFile);
header.bfReserved2 = read_u16(pFile);
header.bfOffBits = read_u32(pFile);

// считываем заголовок изображения
BITMAPINFOHEADER bmiHeader;
bmiHeader.biSize = read_u32(pFile);
bmiHeader.biWidth = read_s32(pFile);
bmiHeader.biHeight = read_s32(pFile);
bmiHeader.biPlanes = read_u16(pFile);
bmiHeader.biBitCount = read_u16(pFile);
bmiHeader.biCompression = read_u32(pFile);
bmiHeader.biSizeImage = read_u32(pFile);
bmiHeader.biXPelsPerMeter = read_s32(pFile);
bmiHeader.biYPelsPerMeter = read_s32(pFile);
bmiHeader.biClrUsed = read_u32(pFile);
bmiHeader.biClrImportant = read_u32(pFile);

RGBQUAD **rgb = new RGBQUAD*[bmiHeader.biWidth];
for (int i = 0; i < bmiHeader.biWidth; i++) {
rgb[i] = new RGBQUAD[bmiHeader.biHeight];
}

int kr = (int)bmiHeader.biWidth * 3 % 4;
if (kr != 0) {kr = 4 - kr; }
for (int j = 0; j < bmiHeader.biHeight; j++) {
for (int i = 0; i < bmiHeader.biWidth; i++) {
rgb[i][j].rgbBlue = getc(pFile);
rgb[i][j].rgbGreen = getc(pFile);
rgb[i][j].rgbRed = getc(pFile);
}

// пропускаем последние 1-3 байта в конце строки для обеспечения кратности 4
for (int i = 0; i < kr; i++) {
getc(pFile);
}
}

// Передаем значения ширины и высоты глобальным переменным
mx = bmiHeader.biWidth;
my = bmiHeader.biHeight;
fclose(pFile);
return rgb;
}

unsigned short read_u16(FILE *fp)
{
unsigned char b0, b1;
b0 = getc(fp);
b1 = getc(fp);
return ((b1 << 8) | b0);
}

unsigned int read_u32(FILE *fp)
{
unsigned char b0, b1, b2, b3;
b0 = getc(fp);
b1 = getc(fp);
b2 = getc(fp);
b3 = getc(fp);
return ((((((b3 << 8) | b2) << 8) | b1) << 8) | b0);
}

int read_s32(FILE *fp)
{
unsigned char b0, b1, b2, b3;
b0 = getc(fp);
b1 = getc(fp);
b2 = getc(fp);
b3 = getc(fp);
return ((int)(((((b3 << 8) | b2) << 8) | b1) << 8) | b0);
}

Формат Bmp (256-цветной)

За информационным заголовком следует таблица цветов, представляющая собой массив из 256 (по числу цветов) 4-байтовых полей. Каждое поле соответствует своему цвету в палитре, а три байта из четырех – компонентам синей, зеленой и красной составляющих для этого цвета. Последний, самый старший байт каждого поля зарезервирован и равен 0.

После таблицы цветов находятся данные изображения, которое по строкам растра записано снизу вверх, а внутри строки – слева направо. Так как на некоторых платформах невозможно считать единицу данных, которая меньше 4 байт, длина каждой строки выровнена на границу в 4 байта, т. е. при длине строки, некратной четырем, она дополняется нулями. Это обстоятельство обязательно надо учитывать при считывании файла.

На нижнем рисунке представлено точечное изображение (10*5) и конец листинга файла, где захвачены несколько полей из таблицы цветов и приводятся данные об изображении (обведены красной линией).

На рисунке выделены по 4 байта цветов палитры, которые используются для указания цвета точек. Белый цвет имеет последний номер в таблице цветов – 255 (FF). Синий цвет указан под номером 252 (FC), зеленый – 250 (FA), красный – 249 (F9). Длина каждой строки дополняется 2-я байтами (00 00) для кратности 4 (10+2=12).

 

Автор: Николай Свирневский

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *