Чтобы сохранить картинку в формате BMP необходимо соблюдение особых правил, а именно соблюдения спецификаций заголовков файла BMP, в данном уроке мы рассматриваем наиболее часто встречаемую спецификацию с заголовками BMP и DIB (14 и 40 байт соответственно).
Прежде чем рассказать о том как строить заголовки BMP и DIB вооружимся необходимой функцией:
void int_to_bytes(char * arr, int n) { for (int i = 0; i < 4; i++) arr[i] = (char)((n >> 8 * i) & 0xFF); }
Она побайтово записывает значение целого числа в массив arr в ПЕРЕВЕРНУТОМ виде.
Для начала открываем файл:
ofstream os(filename, ofstream::binary);
Далее следует идентификатор того, что это файл BMP, а именно байты { 'B', 'M' }.
Идентификатор занимает 2 байта.
Запишем его в файл:
w[0] = 'B'; w[1] = 'M'; os.write(w, 2);
Далее идет размер файла в байтах, подсчитаем его так: (4*width*height + 14 + 40), т.к. на каждый пиксел мы будем тратить по 32 бита (4 байта) и 14 + 40 байтов на заголовки.
Идентификатор занимает 4 байта.
Запишем его в файл:
int_to_bytes(w, width*height*4 + 14 + 40); os.write(w, 4);
Далее следуют байты, специфические вашему приложению, мы в них не заинтересованы, пусть будет 0.
Идентификатор занимает 4 байта.
Запишем его в файл:
int_to_bytes(w, 0); os.write(w, 4);
Далее следует положение массива пикселов самой картинки. Т.к. на заголовки у нас уходит 14+40 байт, значит массив будет находится со смещением 54.
Идентификатор занимает 4 байта.
Запишем его в файл:
int_to_bytes(w, 54); os.write(w, 4);
Теперь начинается заголовок DIB
Указывается размер DIB заголовка, у нас он равен 40 байт.
Идентификатор занимает 4 байта.
Запишем его в файл:
int_to_bytes(w, 40); os.write(w, 4);
Далее следуют размеры изображения, по 4 байта соответственно:
int_to_bytes(w, width); os.write(w, 4); int_to_bytes(w, height); os.write(w, 4);
Количество цветовых плоскостей. У нас одна, 2 байта:
int_to_bytes(w, 1); os.write(w, 2);
Количество бит на пиксел, у нас 32. 2 байта:
int_to_bytes(w, 32); os.write(w, 2);
Метод сжатия. У нас 0 т.е. BI_RGB - никакого сжатия нету, 4 байта:
int_to_bytes(w, 0); os.write(w, 4);
Размер сырого изображения, без заголовков. Может быть указан 0 для изображений BI_RGB - что и есть наш случай. 4 байта:
int_to_bytes(w, 0); os.write(w, 4);
Размер печати - у нас 2834 пиксел на метр по вертикали и горизонтали. По 4 байта:
int_to_bytes(w, 2834); os.write(w, 4); int_to_bytes(w, 2834); os.write(w, 4);
Количество цветов. 0 приравнивается к 2n, где n - битность (32 бита у нас), 4 байта:
int_to_bytes(w, 0); os.write(w, 4);
Количество "важных" цветов, чаще всего игнорируется большинством приложений, может быть 0. 4 байта:
int_to_bytes(w, 0); os.write(w, 4);
А далее сама запись картинки. Необходимо заметить, что для стандарта BMP строки идут с конца изображения:
int last = height - 1; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { os.write(bytes + (4*width*(last - i) + 4*j), 4); } }
Полный код, который создает файл "file.bmp" с нарисований функцией
cos(3*x^3+5*x^2-6*x-8)
#include <fstream> #include <math.h> #include <stdio.h> #include <stdlib.h> using namespace std; #define WIDTH 640 #define HEIGHT 360 void exit_with_msg(const char * msg) { printf(msg); printf("\n"); exit(1); } void int_to_bytes(char * arr, int n) { for (int i = 0; i < 4; i++) arr[i] = (char)((n >> 8 * i) & 0xFF); } void write_bmp(const char * filename, const char * bytes, int width, int height) { if (width % 4 != 0) exit_with_msg("bad width"); ofstream os(filename, ofstream::binary); char w[4]; // working array /* BMP header start */ // BMP identifier w[0] = 'B'; w[1] = 'M'; os.write(w, 2); // file size bytes int_to_bytes(w, width*height*4 + 14 + 40); os.write(w, 4); // application dependent bytes int_to_bytes(w, 0); os.write(w, 4); //array offset int_to_bytes(w, 54); os.write(w, 4); /* BMP header end */ /* DIB header start */ // DIB size int_to_bytes(w, 40); os.write(w, 4); // width and height int_to_bytes(w, width); os.write(w, 4); int_to_bytes(w, height); os.write(w, 4); // color planes int_to_bytes(w, 1); os.write(w, 2); // bits per pixel int_to_bytes(w, 32); os.write(w, 2); // compression method BI_RGB (none) int_to_bytes(w, 0); os.write(w, 4); // raw image size (can be 0 for BI_RGB bitmaps) int_to_bytes(w, 0); os.write(w, 4); // image print resolution int_to_bytes(w, 2834); os.write(w, 4); int_to_bytes(w, 2834); os.write(w, 4); // number of colors (0 defaults to 2^n) int_to_bytes(w, 0); os.write(w, 4); // number of important colors used int_to_bytes(w, 0); os.write(w, 4); /* DIB header end */ /* image start */ int last = height - 1; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { os.write(bytes + (4*width*(last - i) + 4*j), 4); } } /* image end */ os.close(); } float f(float x) { return cos(3*x*x*x+5*x*x-6*x-8); } int main() { // allocate bytes for picutre char * bytes = new char[WIDTH * HEIGHT * 4]; if (bytes == NULL) exit_with_msg("no memory"); // draw picture in memory memset(bytes, 0, WIDTH*HEIGHT*4); for (float x = -5.0f; x < 5.0f; x += 0.0005f) { float y = f(x); int X = (int)(64*(x+5)); int Y = (int)(90*(y+2)); bytes[4*(WIDTH*(HEIGHT-Y) + X)] = 255; } // write picture to file write_bmp("file.bmp", bytes, WIDTH, HEIGHT); return 0; }