1
+ #pragma once
2
+ #include < fstream>
3
+ #include < vector>
4
+ #include < stdexcept>
5
+ #include < iostream>
6
+
7
+ #pragma pack(push, 1)
8
+ struct BMPFileHeader {
9
+ uint16_t file_type{ 0x4D42 }; // File type always BM which is 0x4D42 (stored as hex uint16_t in little endian)
10
+ uint32_t file_size{ 0 }; // Size of the file (in bytes)
11
+ uint16_t reserved1{ 0 }; // Reserved, always 0
12
+ uint16_t reserved2{ 0 }; // Reserved, always 0
13
+ uint32_t offset_data{ 0 }; // Start position of pixel data (bytes from the beginning of the file)
14
+ };
15
+
16
+ struct BMPInfoHeader {
17
+ uint32_t size{ 0 }; // Size of this header (in bytes)
18
+ int32_t width{ 0 }; // width of bitmap in pixels
19
+ int32_t height{ 0 }; // width of bitmap in pixels
20
+ // (if positive, bottom-up, with origin in lower left corner)
21
+ // (if negative, top-down, with origin in upper left corner)
22
+ uint16_t planes{ 1 }; // No. of planes for the target device, this is always 1
23
+ uint16_t bit_count{ 0 }; // No. of bits per pixel
24
+ uint32_t compression{ 0 }; // 0 or 3 - uncompressed. THIS PROGRAM CONSIDERS ONLY UNCOMPRESSED BMP images
25
+ uint32_t size_image{ 0 }; // 0 - for uncompressed images
26
+ int32_t x_pixels_per_meter{ 0 };
27
+ int32_t y_pixels_per_meter{ 0 };
28
+ uint32_t colors_used{ 0 }; // No. color indexes in the color table. Use 0 for the max number of colors allowed by bit_count
29
+ uint32_t colors_important{ 0 }; // No. of colors used for displaying the bitmap. If 0 all colors are required
30
+ };
31
+
32
+ struct BMPColorHeader {
33
+ uint32_t red_mask{ 0x00ff0000 }; // Bit mask for the red channel
34
+ uint32_t green_mask{ 0x0000ff00 }; // Bit mask for the green channel
35
+ uint32_t blue_mask{ 0x000000ff }; // Bit mask for the blue channel
36
+ uint32_t alpha_mask{ 0xff000000 }; // Bit mask for the alpha channel
37
+ uint32_t color_space_type{ 0x73524742 }; // Default "sRGB" (0x73524742)
38
+ uint32_t unused[16 ]{ 0 }; // Unused data for sRGB color space
39
+ };
40
+ #pragma pack(pop)
41
+
42
+ struct BMP {
43
+ BMPFileHeader file_header;
44
+ BMPInfoHeader bmp_info_header;
45
+ BMPColorHeader bmp_color_header;
46
+ std::vector<uint8_t > data;
47
+
48
+ BMP (const char *fname) {
49
+ read (fname);
50
+ }
51
+
52
+ void read (const char *fname) {
53
+ std::ifstream inp{ fname, std::ios_base::binary };
54
+ if (inp) {
55
+ inp.read ((char *)&file_header, sizeof (file_header));
56
+ if (file_header.file_type != 0x4D42 ) {
57
+ throw std::runtime_error (" Error! Unrecognized file format." );
58
+ }
59
+ inp.read ((char *)&bmp_info_header, sizeof (bmp_info_header));
60
+
61
+ // The BMPColorHeader is used only for transparent images
62
+ if (bmp_info_header.bit_count == 32 ) {
63
+ // Check if the file has bit mask color information
64
+ if (bmp_info_header.size >= (sizeof (BMPInfoHeader) + sizeof (BMPColorHeader))) {
65
+ inp.read ((char *)&bmp_color_header, sizeof (bmp_color_header));
66
+ // Check if the pixel data is stored as BGRA and if the color space type is sRGB
67
+ check_color_header (bmp_color_header);
68
+ } else {
69
+ std::cerr << " Error! The file \" " << fname << " \" does not seem to contain bit mask information\n " ;
70
+ throw std::runtime_error (" Error! Unrecognized file format." );
71
+ }
72
+ }
73
+
74
+ // Jump to the pixel data location
75
+ inp.seekg (file_header.offset_data , inp.beg );
76
+
77
+ // Adjust the header fields for output.
78
+ // Some editors will put extra info in the image file, we only save the headers and the data.
79
+ if (bmp_info_header.bit_count == 32 ) {
80
+ bmp_info_header.size = sizeof (BMPInfoHeader) + sizeof (BMPColorHeader);
81
+ file_header.offset_data = sizeof (BMPFileHeader) + sizeof (BMPInfoHeader) + sizeof (BMPColorHeader);
82
+ } else {
83
+ bmp_info_header.size = sizeof (BMPInfoHeader);
84
+ file_header.offset_data = sizeof (BMPFileHeader) + sizeof (BMPInfoHeader);
85
+ }
86
+ file_header.file_size = file_header.offset_data ;
87
+
88
+ if (bmp_info_header.height < 0 ) {
89
+ throw std::runtime_error (" The program can treat only BMP images with the origin in the bottom left corner!" );
90
+ }
91
+
92
+ data.resize (bmp_info_header.width * bmp_info_header.height * bmp_info_header.bit_count / 8 );
93
+
94
+ // Here we check if we need to take into account row padding
95
+ if (bmp_info_header.width % 4 == 0 ) {
96
+ inp.read ((char *)data.data (), data.size ());
97
+ file_header.file_size += static_cast <uint32_t >(data.size ());
98
+ }
99
+ else {
100
+ row_stride = bmp_info_header.width * bmp_info_header.bit_count / 8 ;
101
+ uint32_t new_stride = make_stride_aligned (4 );
102
+ std::vector<uint8_t > padding_row (new_stride - row_stride);
103
+
104
+ for (int y = 0 ; y < bmp_info_header.height ; ++y) {
105
+ inp.read ((char *)(data.data () + row_stride * y), row_stride);
106
+ inp.read ((char *)padding_row.data (), padding_row.size ());
107
+ }
108
+ file_header.file_size += static_cast <uint32_t >(data.size ()) + bmp_info_header.height * static_cast <uint32_t >(padding_row.size ());
109
+ }
110
+ }
111
+ else {
112
+ throw std::runtime_error (" Unable to open the input image file." );
113
+ }
114
+ }
115
+
116
+ BMP (int32_t width, int32_t height, bool has_alpha = true ) {
117
+ if (width <= 0 || height <= 0 ) {
118
+ throw std::runtime_error (" The image width and height must be positive numbers." );
119
+ }
120
+
121
+ bmp_info_header.width = width;
122
+ bmp_info_header.height = height;
123
+ if (has_alpha) {
124
+ bmp_info_header.size = sizeof (BMPInfoHeader) + sizeof (BMPColorHeader);
125
+ file_header.offset_data = sizeof (BMPFileHeader) + sizeof (BMPInfoHeader) + sizeof (BMPColorHeader);
126
+
127
+ bmp_info_header.bit_count = 32 ;
128
+ bmp_info_header.compression = 3 ;
129
+ row_stride = width * 4 ;
130
+ data.resize (row_stride * height);
131
+ file_header.file_size = file_header.offset_data + data.size ();
132
+ }
133
+ else {
134
+ bmp_info_header.size = sizeof (BMPInfoHeader);
135
+ file_header.offset_data = sizeof (BMPFileHeader) + sizeof (BMPInfoHeader);
136
+
137
+ bmp_info_header.bit_count = 24 ;
138
+ bmp_info_header.compression = 0 ;
139
+ row_stride = width * 3 ;
140
+ data.resize (row_stride * height);
141
+
142
+ uint32_t new_stride = make_stride_aligned (4 );
143
+ file_header.file_size = file_header.offset_data + static_cast <uint32_t >(data.size ()) + bmp_info_header.height * (new_stride - row_stride);
144
+ }
145
+ }
146
+
147
+ void write (const char *fname) {
148
+ std::ofstream of{ fname, std::ios_base::binary };
149
+ if (of) {
150
+ if (bmp_info_header.bit_count == 32 ) {
151
+ write_headers_and_data (of);
152
+ }
153
+ else if (bmp_info_header.bit_count == 24 ) {
154
+ if (bmp_info_header.width % 4 == 0 ) {
155
+ write_headers_and_data (of);
156
+ }
157
+ else {
158
+ uint32_t new_stride = make_stride_aligned (4 );
159
+ std::vector<uint8_t > padding_row (new_stride - row_stride);
160
+
161
+ write_headers (of);
162
+
163
+ for (int y = 0 ; y < bmp_info_header.height ; ++y) {
164
+ of.write ((const char *)(data.data () + row_stride * y), row_stride);
165
+ of.write ((const char *)padding_row.data (), padding_row.size ());
166
+ }
167
+ }
168
+ }
169
+ else {
170
+ throw std::runtime_error (" The program can treat only 24 or 32 bits per pixel BMP files" );
171
+ }
172
+ }
173
+ else {
174
+ throw std::runtime_error (" Unable to open the output image file." );
175
+ }
176
+ }
177
+
178
+ void fill_region (uint32_t x0, uint32_t y0, uint32_t w, uint32_t h, uint8_t B, uint8_t G, uint8_t R, uint8_t A) {
179
+ if (x0 + w > (uint32_t )bmp_info_header.width || y0 + h > (uint32_t )bmp_info_header.height ) {
180
+ throw std::runtime_error (" The region does not fit in the image!" );
181
+ }
182
+
183
+ uint32_t channels = bmp_info_header.bit_count / 8 ;
184
+ for (uint32_t y = y0 ; y < y0 + h; ++y) {
185
+ for (uint32_t x = x0; x < x0 + w; ++x) {
186
+ data[channels * (y * bmp_info_header.width + x) + 0 ] = B;
187
+ data[channels * (y * bmp_info_header.width + x) + 1 ] = G;
188
+ data[channels * (y * bmp_info_header.width + x) + 2 ] = R;
189
+ if (channels == 4 ) {
190
+ data[channels * (y * bmp_info_header.width + x) + 3 ] = A;
191
+ }
192
+ }
193
+ }
194
+ }
195
+
196
+ void set_pixel (uint32_t x0, uint32_t y0, uint8_t B, uint8_t G, uint8_t R, uint8_t A) {
197
+ if (x0 >= (uint32_t )bmp_info_header.width || y0 >= (uint32_t )bmp_info_header.height || x0 < 0 || y0 < 0 ) {
198
+ throw std::runtime_error (" The point is outside the image boundaries!" );
199
+ }
200
+
201
+ uint32_t channels = bmp_info_header.bit_count / 8 ;
202
+ data[channels * (y0 * bmp_info_header.width + x0) + 0 ] = B;
203
+ data[channels * (y0 * bmp_info_header.width + x0) + 1 ] = G;
204
+ data[channels * (y0 * bmp_info_header.width + x0) + 2 ] = R;
205
+ if (channels == 4 ) {
206
+ data[channels * (y0 * bmp_info_header.width + x0) + 3 ] = A;
207
+ }
208
+ }
209
+
210
+ void draw_rectangle (uint32_t x0, uint32_t y0, uint32_t w, uint32_t h,
211
+ uint8_t B, uint8_t G, uint8_t R, uint8_t A, uint8_t line_w) {
212
+ if (x0 + w > (uint32_t )bmp_info_header.width || y0 + h > (uint32_t )bmp_info_header.height ) {
213
+ throw std::runtime_error (" The rectangle does not fit in the image!" );
214
+ }
215
+
216
+ fill_region (x0, y0 , w, line_w, B, G, R, A); // top line
217
+ fill_region (x0, (y0 + h - line_w), w, line_w, B, G, R, A); // bottom line
218
+ fill_region ((x0 + w - line_w), (y0 + line_w), line_w, (h - (2 * line_w)), B, G, R, A); // right line
219
+ fill_region (x0, (y0 + line_w), line_w, (h - (2 * line_w)), B, G, R, A); // left line
220
+ }
221
+
222
+ private:
223
+ uint32_t row_stride{ 0 };
224
+
225
+ void write_headers (std::ofstream &of) {
226
+ of.write ((const char *)&file_header, sizeof (file_header));
227
+ of.write ((const char *)&bmp_info_header, sizeof (bmp_info_header));
228
+ if (bmp_info_header.bit_count == 32 ) {
229
+ of.write ((const char *)&bmp_color_header, sizeof (bmp_color_header));
230
+ }
231
+ }
232
+
233
+ void write_headers_and_data (std::ofstream &of) {
234
+ write_headers (of);
235
+ of.write ((const char *)data.data (), data.size ());
236
+ }
237
+
238
+ // Add 1 to the row_stride until it is divisible with align_stride
239
+ uint32_t make_stride_aligned (uint32_t align_stride) {
240
+ uint32_t new_stride = row_stride;
241
+ while (new_stride % align_stride != 0 ) {
242
+ new_stride++;
243
+ }
244
+ return new_stride;
245
+ }
246
+
247
+ // Check if the pixel data is stored as BGRA and if the color space type is sRGB
248
+ void check_color_header (BMPColorHeader &bmp_color_header) {
249
+ BMPColorHeader expected_color_header;
250
+ if (expected_color_header.red_mask != bmp_color_header.red_mask ||
251
+ expected_color_header.blue_mask != bmp_color_header.blue_mask ||
252
+ expected_color_header.green_mask != bmp_color_header.green_mask ||
253
+ expected_color_header.alpha_mask != bmp_color_header.alpha_mask ) {
254
+ throw std::runtime_error (" Unexpected color mask format! The program expects the pixel data to be in the BGRA format" );
255
+ }
256
+ if (expected_color_header.color_space_type != bmp_color_header.color_space_type ) {
257
+ throw std::runtime_error (" Unexpected color space type! The program expects sRGB values" );
258
+ }
259
+ }
260
+ };
0 commit comments