1 | // Aseprite |
2 | // Copyright (C) 2019-2022 Igara Studio S.A. |
3 | // Copyright (C) 2001-2018 David Capello |
4 | // |
5 | // This program is distributed under the terms of |
6 | // the End-User License Agreement for Aseprite. |
7 | // |
8 | // bmp.c - Based on the code of Seymour Shlien and Jonas Petersen. |
9 | // |
10 | // Info about BMP format: |
11 | // https://en.wikipedia.org/wiki/BMP_file_format |
12 | // http://justsolve.archiveteam.org/wiki/BMP |
13 | // https://docs.microsoft.com/en-us/windows/win32/gdi/bitmap-header-types |
14 | |
15 | #ifdef HAVE_CONFIG_H |
16 | #include "config.h" |
17 | #endif |
18 | |
19 | #include "app/file/file.h" |
20 | #include "app/file/file_format.h" |
21 | #include "app/file/format_options.h" |
22 | #include "base/cfile.h" |
23 | #include "base/file_handle.h" |
24 | #include "doc/doc.h" |
25 | #include "fmt/format.h" |
26 | |
27 | namespace app { |
28 | |
29 | // Max supported .bmp size (to filter out invalid image sizes) |
30 | const uint32_t kMaxBmpSize = 1024*1024*128; // 128 MB |
31 | |
32 | using namespace base; |
33 | |
34 | class BmpFormat : public FileFormat { |
35 | enum { |
36 | BMP_OPTIONS_FORMAT_WINDOWS = 12, |
37 | BMP_OPTIONS_FORMAT_OS2 = 40, |
38 | BMP_OPTIONS_COMPRESSION_RGB = 0, |
39 | BMP_OPTIONS_COMPRESSION_RLE8 = 1, |
40 | BMP_OPTIONS_COMPRESSION_RLE4 = 2, |
41 | BMP_OPTIONS_COMPRESSION_BITFIELDS = 3 |
42 | }; |
43 | |
44 | // Data for BMP files |
45 | class BmpOptions : public FormatOptions |
46 | { |
47 | public: |
48 | int format; // bmp format. |
49 | int compression; // bmp compression. |
50 | int bits_per_pixel; // Bits per pixel. |
51 | uint32_t red_mask; // Mask for red channel. |
52 | uint32_t green_mask; // Mask for green channel. |
53 | uint32_t blue_mask; // Mask for blue channel. |
54 | uint32_t alpha_mask; // Mask for alpha channel. |
55 | }; |
56 | |
57 | const char* onGetName() const override { |
58 | return "bmp" ; |
59 | } |
60 | |
61 | void onGetExtensions(base::paths& exts) const override { |
62 | exts.push_back("bmp" ); |
63 | } |
64 | |
65 | dio::FileFormat onGetDioFormat() const override { |
66 | return dio::FileFormat::BMP_IMAGE; |
67 | } |
68 | |
69 | int onGetFlags() const override { |
70 | return |
71 | FILE_SUPPORT_LOAD | |
72 | FILE_SUPPORT_SAVE | |
73 | FILE_SUPPORT_RGB | |
74 | FILE_SUPPORT_RGBA | |
75 | FILE_SUPPORT_GRAY | |
76 | FILE_SUPPORT_INDEXED | |
77 | FILE_SUPPORT_SEQUENCES | |
78 | FILE_ENCODE_ABSTRACT_IMAGE; |
79 | } |
80 | |
81 | bool onLoad(FileOp* fop) override; |
82 | #ifdef ENABLE_SAVE |
83 | bool onSave(FileOp* fop) override; |
84 | #endif |
85 | }; |
86 | |
87 | FileFormat* CreateBmpFormat() |
88 | { |
89 | return new BmpFormat; |
90 | } |
91 | |
92 | #define BI_RGB 0 |
93 | #define BI_RLE8 1 |
94 | #define BI_RLE4 2 |
95 | #define BI_BITFIELDS 3 |
96 | #define BI_ALPHABITFIELDS 6 |
97 | |
98 | #define 14 |
99 | |
100 | #define 12 |
101 | #define 16 |
102 | #define 24 |
103 | #define 40 |
104 | #define 52 |
105 | #define 56 |
106 | #define 60 |
107 | #define 64 |
108 | #define 108 |
109 | #define 124 |
110 | |
111 | struct |
112 | { |
113 | uint32_t ; |
114 | uint32_t ; |
115 | uint16_t ; |
116 | uint16_t ; |
117 | uint32_t ; |
118 | }; |
119 | |
120 | // Used for all Info Header Sizes. |
121 | // Contains only the parameters needed to load the image. |
122 | struct |
123 | { |
124 | uint32_t ; |
125 | uint32_t ; |
126 | uint32_t ; |
127 | uint16_t ; |
128 | uint32_t ; |
129 | uint32_t ; |
130 | uint32_t ; |
131 | uint32_t ; |
132 | uint32_t ; |
133 | uint32_t ; |
134 | |
135 | bool () const |
136 | { |
137 | return biSize >= BV2INFOHEADERSIZE || |
138 | biCompression == BI_BITFIELDS || |
139 | biCompression == BI_ALPHABITFIELDS; |
140 | }; |
141 | bool () const |
142 | { |
143 | return biSize >= BV3INFOHEADERSIZE || |
144 | biCompression == BI_ALPHABITFIELDS; |
145 | }; |
146 | }; |
147 | |
148 | struct // Size: 16 to 64 |
149 | { |
150 | uint32_t ; |
151 | uint32_t ; |
152 | uint16_t ; |
153 | uint16_t ; |
154 | uint32_t ; |
155 | uint32_t ; |
156 | uint32_t ; |
157 | uint32_t ; |
158 | uint32_t ; |
159 | uint32_t ; |
160 | |
161 | uint32_t ; |
162 | uint32_t ; |
163 | uint32_t ; |
164 | uint32_t ; |
165 | }; |
166 | |
167 | struct // Size: 12. |
168 | { |
169 | uint16_t ; |
170 | uint16_t ; |
171 | uint16_t ; |
172 | uint16_t ; |
173 | }; |
174 | |
175 | // TO DO: support ICC profiles and colorimetry |
176 | struct CIEXYZ |
177 | { |
178 | uint32_t ciexyzX; // Fix Point: 2 bits integer part, 30 bits to fractional part |
179 | uint32_t ciexyzY; |
180 | uint32_t ciexyzZ; |
181 | }; |
182 | |
183 | struct CIEXYZTRIPLE |
184 | { |
185 | CIEXYZ ciexyzRed; |
186 | CIEXYZ ciexyzGreen; |
187 | CIEXYZ ciexyzBlue; |
188 | }; |
189 | |
190 | struct // Size: 108. |
191 | { |
192 | uint32_t ; |
193 | uint32_t ; |
194 | uint16_t ; |
195 | uint16_t ; |
196 | uint32_t ; |
197 | uint32_t ; |
198 | uint32_t ; |
199 | uint32_t ; |
200 | uint32_t ; |
201 | uint32_t ; |
202 | |
203 | uint32_t ; |
204 | uint32_t ; |
205 | uint32_t ; |
206 | uint32_t ; |
207 | // TO DO: support ICC profiles and colorimetry |
208 | uint32_t ; |
209 | CIEXYZTRIPLE ; |
210 | uint32_t ; |
211 | uint32_t ; |
212 | uint32_t ; |
213 | }; |
214 | |
215 | struct // Size: 124. |
216 | { |
217 | uint32_t ; |
218 | uint32_t ; |
219 | uint16_t ; |
220 | uint16_t ; |
221 | uint32_t ; |
222 | |
223 | uint32_t ; |
224 | uint32_t ; |
225 | uint32_t ; |
226 | uint32_t ; |
227 | uint32_t ; |
228 | |
229 | uint32_t ; |
230 | uint32_t ; |
231 | uint32_t ; |
232 | uint32_t ; |
233 | // TO DO: support ICC profiles and colorimetry |
234 | uint32_t ; |
235 | CIEXYZTRIPLE ; |
236 | |
237 | uint32_t ; |
238 | uint32_t ; |
239 | uint32_t ; |
240 | |
241 | uint32_t ; |
242 | uint32_t ; |
243 | uint32_t ; |
244 | uint32_t ; |
245 | }; |
246 | |
247 | /* read_bmfileheader: |
248 | * Reads a BMP file header and check that it has the BMP magic number. |
249 | */ |
250 | static int (FILE *f, BITMAPFILEHEADER *) |
251 | { |
252 | fileheader->bfType = fgetw(f); |
253 | fileheader->bfSize = fgetl(f); |
254 | fileheader->bfReserved1 = fgetw(f); |
255 | fileheader->bfReserved2 = fgetw(f); |
256 | fileheader->bfOffBits = fgetl(f); |
257 | |
258 | if (fileheader->bfType != 19778) |
259 | return -1; |
260 | |
261 | return 0; |
262 | } |
263 | |
264 | /* read_win_bminfoheader: |
265 | * Reads information from a BMP file header. |
266 | */ |
267 | static int (FILE *f, BITMAPINFOHEADER *) |
268 | { |
269 | WINBMPINFOHEADER ; |
270 | |
271 | int biSize = infoheader->biSize; |
272 | |
273 | if (biSize != OS22INFOHEADERSIZE16 && |
274 | biSize != OS22INFOHEADERSIZE24 && |
275 | biSize != WININFOHEADERSIZE && |
276 | biSize != BV2INFOHEADERSIZE && |
277 | biSize != BV3INFOHEADERSIZE && |
278 | biSize != OS22INFOHEADERSIZE60 && |
279 | biSize != OS22INFOHEADERSIZE64) |
280 | return -1; |
281 | |
282 | win_infoheader.biWidth = fgetl(f); |
283 | win_infoheader.biHeight = fgetl(f); |
284 | win_infoheader.biPlanes = fgetw(f); |
285 | win_infoheader.biBitCount = fgetw(f); // = 16 bytes |
286 | |
287 | win_infoheader.redMask = 0; |
288 | win_infoheader.greenMask = 0; |
289 | win_infoheader.blueMask = 0; |
290 | win_infoheader.alphaMask = 0; |
291 | if (biSize == OS22INFOHEADERSIZE16) |
292 | win_infoheader.biCompression = BI_RGB; // = 16 bytes |
293 | else { |
294 | ASSERT(biSize >= OS22INFOHEADERSIZE24); |
295 | win_infoheader.biCompression = fgetl(f); |
296 | win_infoheader.biSizeImage = fgetl(f); // = 24 bytes |
297 | |
298 | if (biSize >= WININFOHEADERSIZE) { |
299 | win_infoheader.biXPelsPerMeter = fgetl(f); |
300 | win_infoheader.biYPelsPerMeter = fgetl(f); |
301 | win_infoheader.biClrUsed = fgetl(f); |
302 | win_infoheader.biClrImportant = fgetl(f); // = 40 bytes (WININFOHEADERSIZE) |
303 | |
304 | // 'biCompression' is needed to execute |
305 | // infoheader->isRGBMasks() and infoheader->isAlphaMask() |
306 | infoheader->biCompression = win_infoheader.biCompression; |
307 | |
308 | if (infoheader->isRGBMasks()) { |
309 | win_infoheader.redMask = fgetl(f); |
310 | win_infoheader.greenMask = fgetl(f); |
311 | win_infoheader.blueMask = fgetl(f); // = 52 bytes (BV2INFOHEADERSIZE) |
312 | if (infoheader->isAlphaMask()) { |
313 | win_infoheader.alphaMask = fgetl(f); // = 56 bytes (BV3INFOHEADERSIZE) |
314 | if (biSize >= OS22INFOHEADERSIZE60) { |
315 | fgetl(f); // <--discarded // = 60 bytes |
316 | if (biSize == OS22INFOHEADERSIZE64) |
317 | fgetl(f); // <--discarded // = 64 bytes |
318 | } |
319 | } |
320 | } |
321 | } |
322 | } |
323 | |
324 | infoheader->biWidth = win_infoheader.biWidth; |
325 | infoheader->biHeight = win_infoheader.biHeight; |
326 | infoheader->biBitCount = win_infoheader.biBitCount; |
327 | infoheader->biCompression = win_infoheader.biCompression; |
328 | infoheader->biClrUsed = win_infoheader.biClrUsed; |
329 | infoheader->rMask = win_infoheader.redMask; |
330 | infoheader->gMask = win_infoheader.greenMask; |
331 | infoheader->bMask = win_infoheader.blueMask; |
332 | infoheader->aMask = win_infoheader.alphaMask; |
333 | |
334 | return 0; |
335 | } |
336 | |
337 | /* read_os2_bminfoheader: |
338 | * Reads information from an OS/2 format BMP file header. |
339 | */ |
340 | static int (FILE *f, BITMAPINFOHEADER *) |
341 | { |
342 | OS2BMPINFOHEADER ; |
343 | |
344 | os2_infoheader.biWidth = fgetw(f); |
345 | os2_infoheader.biHeight = fgetw(f); |
346 | os2_infoheader.biPlanes = fgetw(f); |
347 | os2_infoheader.biBitCount = fgetw(f); |
348 | |
349 | infoheader->biWidth = os2_infoheader.biWidth; |
350 | infoheader->biHeight = os2_infoheader.biHeight; |
351 | infoheader->biBitCount = os2_infoheader.biBitCount; |
352 | infoheader->biCompression = 0; |
353 | infoheader->biClrUsed = -1; // Not defined in this format |
354 | |
355 | return 0; |
356 | } |
357 | |
358 | /* read_v4_bminfoheader: |
359 | * Reads information from an V4 format BMP file header. |
360 | */ |
361 | static int (FILE *f, BITMAPINFOHEADER *) |
362 | { |
363 | BMPV4HEADER ; |
364 | |
365 | v4_infoheader.bV4Width = fgetl(f); |
366 | v4_infoheader.bV4Height = fgetl(f); |
367 | v4_infoheader.bV4Planes = fgetw(f); |
368 | v4_infoheader.bV4BitCount = fgetw(f); |
369 | v4_infoheader.bV4Compression = fgetl(f); |
370 | |
371 | v4_infoheader.bV4SizeImage = fgetl(f); |
372 | v4_infoheader.bV4XPelsPerMeter = fgetl(f); |
373 | v4_infoheader.bV4YPelsPerMeter = fgetl(f); |
374 | v4_infoheader.bV4ClrUsed = fgetl(f); |
375 | v4_infoheader.bV4ClrImportant = fgetl(f); |
376 | |
377 | v4_infoheader.bV4RedMask = fgetl(f); |
378 | v4_infoheader.bV4GreenMask = fgetl(f); |
379 | v4_infoheader.bV4BlueMask = fgetl(f); |
380 | v4_infoheader.bV4AlphaMask = fgetl(f); |
381 | |
382 | // TO DO: support ICC profiles and colorimetry |
383 | v4_infoheader.bV4CSType = fgetl(f); |
384 | |
385 | // CIEXYZTRIPLE { |
386 | v4_infoheader.bV4Endpoints.ciexyzRed.ciexyzX = fgetl(f); |
387 | v4_infoheader.bV4Endpoints.ciexyzRed.ciexyzY = fgetl(f); |
388 | v4_infoheader.bV4Endpoints.ciexyzRed.ciexyzZ = fgetl(f); |
389 | |
390 | v4_infoheader.bV4Endpoints.ciexyzGreen.ciexyzX = fgetl(f); |
391 | v4_infoheader.bV4Endpoints.ciexyzGreen.ciexyzY = fgetl(f); |
392 | v4_infoheader.bV4Endpoints.ciexyzGreen.ciexyzZ = fgetl(f); |
393 | |
394 | v4_infoheader.bV4Endpoints.ciexyzBlue.ciexyzX = fgetl(f); |
395 | v4_infoheader.bV4Endpoints.ciexyzBlue.ciexyzY = fgetl(f); |
396 | v4_infoheader.bV4Endpoints.ciexyzBlue.ciexyzZ = fgetl(f); |
397 | // } CIEXYZTRIPLE |
398 | |
399 | v4_infoheader.bV4GammaRed = fgetl(f); |
400 | v4_infoheader.bV4GammaGreen = fgetl(f); |
401 | v4_infoheader.bV4GammaBlue = fgetl(f); |
402 | |
403 | infoheader->biWidth = v4_infoheader.bV4Width; |
404 | infoheader->biHeight = v4_infoheader.bV4Height; |
405 | infoheader->biBitCount = v4_infoheader.bV4BitCount; |
406 | infoheader->biCompression = v4_infoheader.bV4Compression; |
407 | infoheader->biClrUsed = v4_infoheader.bV4ClrUsed; |
408 | |
409 | infoheader->rMask = v4_infoheader.bV4RedMask; |
410 | infoheader->gMask = v4_infoheader.bV4GreenMask; |
411 | infoheader->bMask = v4_infoheader.bV4BlueMask; |
412 | infoheader->aMask = v4_infoheader.bV4AlphaMask; |
413 | |
414 | return 0; |
415 | } |
416 | |
417 | /* read_v5_bminfoheader: |
418 | * Reads information from an V5 format BMP file header. |
419 | */ |
420 | static int (FILE *f, BITMAPINFOHEADER *) |
421 | { |
422 | BMPV5HEADER ; |
423 | |
424 | v5_infoheader.bV5Width = fgetl(f); |
425 | v5_infoheader.bV5Height = fgetl(f); |
426 | v5_infoheader.bV5Planes = fgetw(f); |
427 | v5_infoheader.bV5BitCount = fgetw(f); |
428 | v5_infoheader.bV5Compression = fgetl(f); |
429 | |
430 | v5_infoheader.bV5SizeImage = fgetl(f); |
431 | v5_infoheader.bV5XPelsPerMeter = fgetl(f); |
432 | v5_infoheader.bV5YPelsPerMeter = fgetl(f); |
433 | v5_infoheader.bV5ClrUsed = fgetl(f); |
434 | v5_infoheader.bV5ClrImportant = fgetl(f); |
435 | |
436 | v5_infoheader.bV5RedMask = fgetl(f); |
437 | v5_infoheader.bV5GreenMask = fgetl(f); |
438 | v5_infoheader.bV5BlueMask = fgetl(f); |
439 | v5_infoheader.bV5AlphaMask = fgetl(f); |
440 | |
441 | // TO DO: support ICC profiles and colorimetry |
442 | v5_infoheader.bV5CSType = fgetl(f); |
443 | |
444 | // CIEXYZTRIPLE { |
445 | v5_infoheader.bV5Endpoints.ciexyzRed.ciexyzX = fgetl(f); |
446 | v5_infoheader.bV5Endpoints.ciexyzRed.ciexyzY = fgetl(f); |
447 | v5_infoheader.bV5Endpoints.ciexyzRed.ciexyzZ = fgetl(f); |
448 | |
449 | v5_infoheader.bV5Endpoints.ciexyzGreen.ciexyzX = fgetl(f); |
450 | v5_infoheader.bV5Endpoints.ciexyzGreen.ciexyzY = fgetl(f); |
451 | v5_infoheader.bV5Endpoints.ciexyzGreen.ciexyzZ = fgetl(f); |
452 | |
453 | v5_infoheader.bV5Endpoints.ciexyzBlue.ciexyzX = fgetl(f); |
454 | v5_infoheader.bV5Endpoints.ciexyzBlue.ciexyzY = fgetl(f); |
455 | v5_infoheader.bV5Endpoints.ciexyzBlue.ciexyzZ = fgetl(f); |
456 | // } CIEXYZTRIPLE |
457 | |
458 | v5_infoheader.bV5GammaRed = fgetl(f); |
459 | v5_infoheader.bV5GammaGreen = fgetl(f); |
460 | v5_infoheader.bV5GammaBlue = fgetl(f); |
461 | |
462 | v5_infoheader.bV5Intent = fgetl(f); |
463 | v5_infoheader.bV5ProfileData = fgetl(f); |
464 | v5_infoheader.bV5ProfileSize = fgetl(f); |
465 | fgetl(f); // <-- Reserved DWORD |
466 | |
467 | infoheader->biWidth = v5_infoheader.bV5Width; |
468 | infoheader->biHeight = v5_infoheader.bV5Height; |
469 | infoheader->biBitCount = v5_infoheader.bV5BitCount; |
470 | infoheader->biCompression = v5_infoheader.bV5Compression; |
471 | infoheader->biClrUsed = v5_infoheader.bV5ClrUsed; |
472 | |
473 | infoheader->rMask = v5_infoheader.bV5RedMask; |
474 | infoheader->gMask = v5_infoheader.bV5GreenMask; |
475 | infoheader->bMask = v5_infoheader.bV5BlueMask; |
476 | infoheader->aMask = v5_infoheader.bV5AlphaMask; |
477 | |
478 | return 0; |
479 | } |
480 | |
481 | /* read_bmicolors: |
482 | * Loads the color palette for 1,4,8 bit formats. |
483 | */ |
484 | static void read_bmicolors(FileOp* fop, int bytes, FILE *f, bool win_flag) |
485 | { |
486 | int i, j, r, g, b; |
487 | |
488 | for (i=j=0; i+3 <= bytes && j < 256; ) { |
489 | b = fgetc(f); |
490 | g = fgetc(f); |
491 | r = fgetc(f); |
492 | |
493 | fop->sequenceSetColor(j, r, g, b); |
494 | |
495 | j++; |
496 | i += 3; |
497 | |
498 | if (win_flag && i < bytes) { |
499 | fgetc(f); |
500 | i++; |
501 | } |
502 | } |
503 | |
504 | // Set the number of colors in the palette |
505 | fop->sequenceSetNColors(j); |
506 | |
507 | for (; i<bytes; i++) |
508 | fgetc(f); |
509 | } |
510 | |
511 | /* read_1bit_line: |
512 | * Support function for reading the 1 bit bitmap file format. |
513 | */ |
514 | static void read_1bit_line(int length, FILE *f, Image *image, int line) |
515 | { |
516 | unsigned char b[32]; |
517 | unsigned long n; |
518 | int i, j, k; |
519 | int pix; |
520 | |
521 | for (i=0; i<length; i++) { |
522 | j = i % 32; |
523 | if (j == 0) { |
524 | n = fgetl(f); |
525 | n = |
526 | ((n&0x000000ff)<<24) | |
527 | ((n&0x0000ff00)<< 8) | |
528 | ((n&0x00ff0000)>> 8) | |
529 | ((n&0xff000000)>>24); |
530 | for (k=0; k<32; k++) { |
531 | b[31-k] = (char)(n & 1); |
532 | n = n >> 1; |
533 | } |
534 | } |
535 | pix = b[j]; |
536 | put_pixel(image, i, line, pix); |
537 | } |
538 | } |
539 | |
540 | /* read_2bit_line (not standard): |
541 | * Support function for reading the 2 bit bitmap file format. |
542 | */ |
543 | static void read_2bit_line(int length, FILE *f, Image *image, int line) |
544 | { |
545 | unsigned char b[16]; |
546 | unsigned long n; |
547 | int i, j, k; |
548 | int temp; |
549 | int pix; |
550 | |
551 | for (i=0; i<length; i++) { |
552 | j = i % 16; |
553 | if (j == 0) { |
554 | n = fgetl(f); |
555 | for (k=0; k<4; k++) { |
556 | temp = n & 255; |
557 | b[k*4+3] = temp & 3; |
558 | temp = temp >> 2; |
559 | b[k*4+2] = temp & 3; |
560 | temp = temp >> 2; |
561 | b[k*4+1] = temp & 3; |
562 | temp = temp >> 2; |
563 | b[k*4] = temp & 3; |
564 | n = n >> 8; |
565 | } |
566 | } |
567 | pix = b[j]; |
568 | put_pixel(image, i, line, pix); |
569 | } |
570 | } |
571 | |
572 | /* read_4bit_line: |
573 | * Support function for reading the 4 bit bitmap file format. |
574 | */ |
575 | static void read_4bit_line(int length, FILE *f, Image *image, int line) |
576 | { |
577 | unsigned char b[8]; |
578 | unsigned long n; |
579 | int i, j, k; |
580 | int temp; |
581 | int pix; |
582 | |
583 | for (i=0; i<length; i++) { |
584 | j = i % 8; |
585 | if (j == 0) { |
586 | n = fgetl(f); |
587 | for (k=0; k<4; k++) { |
588 | temp = n & 255; |
589 | b[k*2+1] = temp & 15; |
590 | temp = temp >> 4; |
591 | b[k*2] = temp & 15; |
592 | n = n >> 8; |
593 | } |
594 | } |
595 | pix = b[j]; |
596 | put_pixel(image, i, line, pix); |
597 | } |
598 | } |
599 | |
600 | /* read_8bit_line: |
601 | * Support function for reading the 8 bit bitmap file format. |
602 | */ |
603 | static void read_8bit_line(int length, FILE *f, Image *image, int line) |
604 | { |
605 | unsigned char b[4]; |
606 | unsigned long n; |
607 | int i, j, k; |
608 | int pix; |
609 | |
610 | for (i=0; i<length; i++) { |
611 | j = i % 4; |
612 | if (j == 0) { |
613 | n = fgetl(f); |
614 | for (k=0; k<4; k++) { |
615 | b[k] = (char)(n & 255); |
616 | n = n >> 8; |
617 | } |
618 | } |
619 | pix = b[j]; |
620 | put_pixel(image, i, line, pix); |
621 | } |
622 | } |
623 | |
624 | static void read_16bit_line(int length, FILE *f, Image *image, int line, bool& withAlpha) |
625 | { |
626 | int i, r, g, b, a, word; |
627 | |
628 | for (i=0; i<length; i++) { |
629 | word = fgetw(f); |
630 | |
631 | r = (word >> 10) & 0x1f; |
632 | g = (word >> 5) & 0x1f; |
633 | b = (word) & 0x1f; |
634 | a = (word & 0x8000 ? 255 : 0); |
635 | if (a) |
636 | withAlpha = true; |
637 | put_pixel(image, i, line, |
638 | rgba(scale_5bits_to_8bits(r), |
639 | scale_5bits_to_8bits(g), |
640 | scale_5bits_to_8bits(b), a)); |
641 | } |
642 | |
643 | i = (2*i) % 4; |
644 | if (i > 0) |
645 | while (i++ < 4) |
646 | fgetc(f); |
647 | } |
648 | |
649 | static void read_24bit_line(int length, FILE *f, Image *image, int line) |
650 | { |
651 | int i, r, g, b; |
652 | |
653 | for (i=0; i<length; i++) { |
654 | b = fgetc(f); |
655 | g = fgetc(f); |
656 | r = fgetc(f); |
657 | put_pixel(image, i, line, rgba(r, g, b, 255)); |
658 | } |
659 | |
660 | i = (3*i) % 4; |
661 | if (i > 0) |
662 | while (i++ < 4) |
663 | fgetc(f); |
664 | } |
665 | |
666 | static void read_32bit_line(int length, FILE *f, Image *image, int line, |
667 | bool& withAlpha) |
668 | { |
669 | int i, r, g, b, a; |
670 | |
671 | for (i=0; i<length; i++) { |
672 | b = fgetc(f); |
673 | g = fgetc(f); |
674 | r = fgetc(f); |
675 | a = fgetc(f); |
676 | if (a) |
677 | withAlpha = true; |
678 | put_pixel(image, i, line, rgba(r, g, b, a)); |
679 | } |
680 | } |
681 | |
682 | /* read_image: |
683 | * For reading the noncompressed BMP image format. |
684 | */ |
685 | static void (FILE *f, Image *image, const BITMAPINFOHEADER *, FileOp *fop, bool& withAlpha) |
686 | { |
687 | int i, line, height, dir; |
688 | |
689 | height = (int)infoheader->biHeight; |
690 | line = height < 0 ? 0: height-1; |
691 | dir = height < 0 ? 1: -1; |
692 | height = ABS(height); |
693 | |
694 | for (i=0; i<height; i++, line+=dir) { |
695 | switch (infoheader->biBitCount) { |
696 | case 1: read_1bit_line(infoheader->biWidth, f, image, line); break; |
697 | case 2: read_2bit_line(infoheader->biWidth, f, image, line); break; |
698 | case 4: read_4bit_line(infoheader->biWidth, f, image, line); break; |
699 | case 8: read_8bit_line(infoheader->biWidth, f, image, line); break; |
700 | case 16: read_16bit_line(infoheader->biWidth, f, image, line, withAlpha); break; |
701 | case 24: read_24bit_line(infoheader->biWidth, f, image, line); break; |
702 | case 32: read_32bit_line(infoheader->biWidth, f, image, line, withAlpha); break; |
703 | } |
704 | |
705 | fop->setProgress((float)(i+1) / (float)(height)); |
706 | if (fop->isStop()) |
707 | break; |
708 | } |
709 | |
710 | if ((infoheader->biBitCount == 32 || |
711 | infoheader->biBitCount == 16 ) && !withAlpha) { |
712 | LockImageBits<RgbTraits> imageBits(image, image->bounds()); |
713 | auto imgIt = imageBits.begin(), imgEnd = imageBits.end(); |
714 | for (; imgIt != imgEnd; ++imgIt) |
715 | *imgIt |= 0xff000000; |
716 | } |
717 | } |
718 | |
719 | /* read_rle8_compressed_image: |
720 | * For reading the 8 bit RLE compressed BMP image format. |
721 | * |
722 | * @note This support compressed top-down bitmaps, the MSDN says that |
723 | * they can't exist, but Photoshop can create them. |
724 | */ |
725 | static void (FILE *f, Image *image, const BITMAPINFOHEADER *) |
726 | { |
727 | unsigned char count, val, val0; |
728 | int j, pos, line, height, dir; |
729 | int eolflag, eopicflag; |
730 | |
731 | eopicflag = 0; |
732 | |
733 | height = (int)infoheader->biHeight; |
734 | line = height < 0 ? 0: height-1; |
735 | dir = height < 0 ? 1: -1; |
736 | height = ABS(height); |
737 | |
738 | while (eopicflag == 0) { |
739 | pos = 0; /* x position in bitmap */ |
740 | eolflag = 0; /* end of line flag */ |
741 | |
742 | while ((eolflag == 0) && (eopicflag == 0)) { |
743 | count = fgetc(f); |
744 | val = fgetc(f); |
745 | |
746 | if (count > 0) { /* repeat pixel count times */ |
747 | for (j=0;j<count;j++) { |
748 | put_pixel(image, pos, line, val); |
749 | pos++; |
750 | } |
751 | } |
752 | else { |
753 | switch (val) { |
754 | |
755 | case 0: /* end of line flag */ |
756 | eolflag=1; |
757 | break; |
758 | |
759 | case 1: /* end of picture flag */ |
760 | eopicflag=1; |
761 | break; |
762 | |
763 | case 2: /* displace picture */ |
764 | count = fgetc(f); |
765 | val = fgetc(f); |
766 | pos += count; |
767 | line += val*dir; |
768 | break; |
769 | |
770 | default: /* read in absolute mode */ |
771 | for (j=0; j<val; j++) { |
772 | val0 = fgetc(f); |
773 | put_pixel(image, pos, line, val0); |
774 | pos++; |
775 | } |
776 | |
777 | if (j%2 == 1) |
778 | val0 = fgetc(f); /* align on word boundary */ |
779 | break; |
780 | |
781 | } |
782 | } |
783 | |
784 | if (pos-1 > (int)infoheader->biWidth) |
785 | eolflag=1; |
786 | } |
787 | |
788 | line += dir; |
789 | if (line < 0 || line >= height) |
790 | eopicflag = 1; |
791 | } |
792 | } |
793 | |
794 | /* read_rle4_compressed_image: |
795 | * For reading the 4 bit RLE compressed BMP image format. |
796 | * |
797 | * @note This support compressed top-down bitmaps, the MSDN says that |
798 | * they can't exist, but Photoshop can create them. |
799 | */ |
800 | static void (FILE *f, Image *image, const BITMAPINFOHEADER *) |
801 | { |
802 | unsigned char b[8]; |
803 | unsigned char count; |
804 | unsigned short val0, val; |
805 | int j, k, pos, line, height, dir; |
806 | int eolflag, eopicflag; |
807 | |
808 | eopicflag = 0; /* end of picture flag */ |
809 | |
810 | height = (int)infoheader->biHeight; |
811 | line = height < 0 ? 0: height-1; |
812 | dir = height < 0 ? 1: -1; |
813 | height = ABS(height); |
814 | |
815 | while (eopicflag == 0) { |
816 | pos = 0; |
817 | eolflag = 0; /* end of line flag */ |
818 | |
819 | while ((eolflag == 0) && (eopicflag == 0)) { |
820 | count = fgetc(f); |
821 | val = fgetc(f); |
822 | |
823 | if (count > 0) { /* repeat pixels count times */ |
824 | b[1] = val & 15; |
825 | b[0] = (val >> 4) & 15; |
826 | for (j=0; j<count; j++) { |
827 | put_pixel(image, pos, line, b[j%2]); |
828 | pos++; |
829 | } |
830 | } |
831 | else { |
832 | switch (val) { |
833 | |
834 | case 0: /* end of line */ |
835 | eolflag=1; |
836 | break; |
837 | |
838 | case 1: /* end of picture */ |
839 | eopicflag=1; |
840 | break; |
841 | |
842 | case 2: /* displace image */ |
843 | count = fgetc(f); |
844 | val = fgetc(f); |
845 | pos += count; |
846 | line += val*dir; |
847 | break; |
848 | |
849 | default: /* read in absolute mode */ |
850 | for (j=0; j<val; j++) { |
851 | if ((j%4) == 0) { |
852 | val0 = fgetw(f); |
853 | for (k=0; k<2; k++) { |
854 | b[2*k+1] = val0 & 15; |
855 | val0 = val0 >> 4; |
856 | b[2*k] = val0 & 15; |
857 | val0 = val0 >> 4; |
858 | } |
859 | } |
860 | put_pixel(image, pos, line, b[j%4]); |
861 | pos++; |
862 | } |
863 | break; |
864 | } |
865 | } |
866 | |
867 | if (pos-1 > (int)infoheader->biWidth) |
868 | eolflag=1; |
869 | } |
870 | |
871 | line += dir; |
872 | if (line < 0 || line >= height) |
873 | eopicflag = 1; |
874 | } |
875 | } |
876 | |
877 | static uint32_t calc_shift(const uint32_t channelMask, int& channelBits) |
878 | { |
879 | uint32_t channelShift = 0; |
880 | uint32_t mask = 0; |
881 | if (channelMask) { |
882 | mask = ~channelMask; |
883 | while (mask & 1) { |
884 | ++channelShift; |
885 | mask >>= 1; |
886 | } |
887 | if (mask) { |
888 | mask = ~mask; |
889 | while (mask & 1) { |
890 | channelBits++; |
891 | mask >>= 1; |
892 | } |
893 | } |
894 | else |
895 | channelBits = 32 - channelShift; |
896 | } |
897 | else |
898 | channelBits = 8; |
899 | return channelShift; |
900 | } |
901 | |
902 | static int (FILE *f, Image *image, BITMAPINFOHEADER *, |
903 | uint32_t rmask, uint32_t gmask, uint32_t bmask, |
904 | uint32_t amask, bool& withAlpha) |
905 | { |
906 | uint32_t buffer, rshift, gshift, bshift, ashift; |
907 | int rbits = 0, gbits = 0, bbits = 0, abits = 0; |
908 | int i, j, k, line, height, dir, r, g, b, a; |
909 | int bits_per_pixel; |
910 | int bytes_per_pixel; |
911 | |
912 | height = (int)infoheader->biHeight; |
913 | line = height < 0 ? 0: height-1; |
914 | dir = height < 0 ? 1: -1; |
915 | height = ABS(height); |
916 | |
917 | /* calculate shifts */ |
918 | rshift = calc_shift(rmask, rbits); |
919 | gshift = calc_shift(gmask, gbits); |
920 | bshift = calc_shift(bmask, bbits); |
921 | ashift = calc_shift(amask, abits); |
922 | |
923 | /* calculate bits-per-pixel and bytes-per-pixel */ |
924 | bits_per_pixel = infoheader->biBitCount; |
925 | bytes_per_pixel = ((bits_per_pixel / 8) + |
926 | ((bits_per_pixel % 8) > 0 ? 1: 0)); |
927 | |
928 | for (i=0; i<height; i++, line+=dir) { |
929 | for (j=0; j<(int)infoheader->biWidth; j++) { |
930 | /* read the DWORD, WORD or BYTE in little-endian order */ |
931 | buffer = 0; |
932 | for (k=0; k<bytes_per_pixel; k++) |
933 | buffer |= fgetc(f) << (k<<3); |
934 | |
935 | r = (buffer & rmask) >> rshift; |
936 | g = (buffer & gmask) >> gshift; |
937 | b = (buffer & bmask) >> bshift; |
938 | a = (buffer & amask) >> ashift; |
939 | |
940 | r = (rbits == 8 ? r : scale_xxbits_to_8bits(rbits, r) ); |
941 | g = (gbits == 8 ? g : scale_xxbits_to_8bits(gbits, g) ); |
942 | b = (bbits == 8 ? b : scale_xxbits_to_8bits(bbits, b) ); |
943 | a = (abits == 8 ? a : scale_xxbits_to_8bits(abits, a) ); |
944 | |
945 | if (a) |
946 | withAlpha = true; |
947 | put_pixel_fast<RgbTraits>(image, j, line, rgba(r, g, b, a)); |
948 | } |
949 | |
950 | j = (bytes_per_pixel*j) % 4; |
951 | if (j > 0) |
952 | while (j++ < 4) |
953 | fgetc(f); |
954 | } |
955 | |
956 | if (!withAlpha) { |
957 | LockImageBits<RgbTraits> imageBits(image, image->bounds()); |
958 | auto imgIt = imageBits.begin(), imgEnd = imageBits.end(); |
959 | for (; imgIt != imgEnd; ++imgIt) |
960 | *imgIt |= 0xff000000; |
961 | } |
962 | |
963 | return 0; |
964 | } |
965 | |
966 | bool BmpFormat::onLoad(FileOp *fop) |
967 | { |
968 | uint32_t rmask, gmask, bmask, amask; |
969 | BITMAPFILEHEADER ; |
970 | BITMAPINFOHEADER ; |
971 | PixelFormat pixelFormat; |
972 | int format; |
973 | |
974 | FileHandle handle(open_file_with_exception(fop->filename(), "rb" )); |
975 | FILE* f = handle.get(); |
976 | |
977 | if (read_bmfileheader(f, &fileheader) != 0) |
978 | return false; |
979 | |
980 | infoheader.biSize = fgetl(f); |
981 | |
982 | if (infoheader.biSize >= 16 && infoheader.biSize <= 64) { |
983 | format = BMP_OPTIONS_FORMAT_WINDOWS; |
984 | |
985 | if (read_win_bminfoheader(f, &infoheader) != 0) { |
986 | return false; |
987 | } |
988 | if (infoheader.biCompression != BI_BITFIELDS && |
989 | infoheader.biCompression != BI_ALPHABITFIELDS) |
990 | read_bmicolors(fop, |
991 | fileheader.bfOffBits - infoheader.biSize - OS2FILEHEADERSIZE, |
992 | f, true); |
993 | else if (infoheader.biBitCount <= 8) |
994 | return false; |
995 | } |
996 | else if (infoheader.biSize == OS2INFOHEADERSIZE) { |
997 | format = BMP_OPTIONS_FORMAT_OS2; |
998 | |
999 | if (read_os2_bminfoheader(f, &infoheader) != 0) { |
1000 | return false; |
1001 | } |
1002 | /* compute number of colors recorded */ |
1003 | if (infoheader.biCompression != BI_BITFIELDS && |
1004 | infoheader.biCompression != BI_ALPHABITFIELDS) |
1005 | read_bmicolors(fop, |
1006 | fileheader.bfOffBits - infoheader.biSize - OS2FILEHEADERSIZE, |
1007 | f, false); |
1008 | else if (infoheader.biBitCount <= 8) |
1009 | return false; |
1010 | } |
1011 | else if (infoheader.biSize == BV4INFOHEADERSIZE) { |
1012 | format = BMP_OPTIONS_FORMAT_WINDOWS; |
1013 | |
1014 | if (read_v4_bminfoheader(f, &infoheader) != 0) { |
1015 | return false; |
1016 | } |
1017 | /* compute number of colors recorded */ |
1018 | if (infoheader.biCompression != BI_BITFIELDS && |
1019 | infoheader.biCompression != BI_ALPHABITFIELDS) |
1020 | read_bmicolors(fop, |
1021 | fileheader.bfOffBits - infoheader.biSize - OS2FILEHEADERSIZE, |
1022 | f, true); |
1023 | else if (infoheader.biBitCount <= 8) |
1024 | return false; |
1025 | } |
1026 | else if (infoheader.biSize == BV5INFOHEADERSIZE) { |
1027 | format = BMP_OPTIONS_FORMAT_WINDOWS; |
1028 | |
1029 | if (read_v5_bminfoheader(f, &infoheader) != 0) { |
1030 | return false; |
1031 | } |
1032 | /* compute number of colors recorded */ |
1033 | if (infoheader.biCompression != BI_BITFIELDS && |
1034 | infoheader.biCompression != BI_ALPHABITFIELDS) |
1035 | read_bmicolors(fop, |
1036 | fileheader.bfOffBits - infoheader.biSize - OS2FILEHEADERSIZE, |
1037 | f, true); |
1038 | else if (infoheader.biBitCount <= 8) |
1039 | return false; |
1040 | } |
1041 | else { |
1042 | return false; |
1043 | } |
1044 | |
1045 | // Check compatible Compression |
1046 | if (infoheader.biCompression == 4 || |
1047 | infoheader.biCompression == 5 || |
1048 | infoheader.biCompression > 6) { |
1049 | fop->setError("Unsupported BMP compression.\n" ); |
1050 | return false; |
1051 | } |
1052 | |
1053 | // Check image size is valid |
1054 | { |
1055 | if (int(infoheader.biWidth) < 1 || |
1056 | ABS(int(infoheader.biHeight)) == 0) { |
1057 | fop->setError("Invalid BMP size.\n" ); |
1058 | return false; |
1059 | } |
1060 | |
1061 | uint32_t size = infoheader.biWidth * uint32_t(ABS(int(infoheader.biHeight))); |
1062 | if (infoheader.biBitCount >= 8) |
1063 | size *= (infoheader.biBitCount / 8); |
1064 | else if (8 / infoheader.biBitCount > 0) |
1065 | size /= (8 / infoheader.biBitCount); |
1066 | |
1067 | if (size > kMaxBmpSize) { |
1068 | fop->setError(fmt::format("BMP size unsupported ({:.2f} MB > {:.2f} MB).\n" , |
1069 | size / 1024.0 / 1024.0, |
1070 | kMaxBmpSize / 1024.0 / 1024.0).c_str()); |
1071 | return false; |
1072 | } |
1073 | } |
1074 | |
1075 | if ((infoheader.biBitCount == 32) || |
1076 | (infoheader.biBitCount == 24) || |
1077 | (infoheader.biBitCount == 16)) |
1078 | pixelFormat = IMAGE_RGB; |
1079 | else |
1080 | pixelFormat = IMAGE_INDEXED; |
1081 | |
1082 | /* bitfields have the 'mask' for each component */ |
1083 | if (infoheader.isRGBMasks()) { |
1084 | rmask = infoheader.rMask; |
1085 | gmask = infoheader.gMask; |
1086 | bmask = infoheader.bMask; |
1087 | amask = (infoheader.isAlphaMask() ? infoheader.aMask : 0); |
1088 | } |
1089 | else |
1090 | rmask = gmask = bmask = amask = 0; |
1091 | |
1092 | ImageRef image = fop->sequenceImage(pixelFormat, |
1093 | infoheader.biWidth, |
1094 | ABS((int)infoheader.biHeight)); |
1095 | if (!image) { |
1096 | return false; |
1097 | } |
1098 | |
1099 | if (pixelFormat == IMAGE_RGB) |
1100 | clear_image(image.get(), |
1101 | rgba(0, 0, 0, (infoheader.isAlphaMask() ? 0 : 255))); |
1102 | else |
1103 | clear_image(image.get(), 0); |
1104 | |
1105 | // We indirectly calculate 'on the fly' if the BMP file |
1106 | // has all its pixels with alpha value equal to 0 (i.e. BMP |
1107 | // without alpha channel) or if there is at least one pixel |
1108 | // with non 0 alpha (i.e. BMP works with alpha channel). |
1109 | // The result of this analysis will be stored in the boolean 'withAlpha'. |
1110 | bool withAlpha = false; |
1111 | switch (infoheader.biCompression) { |
1112 | |
1113 | case BI_RGB: |
1114 | read_image(f, image.get(), &infoheader, fop, withAlpha); |
1115 | break; |
1116 | |
1117 | case BI_RLE8: |
1118 | read_rle8_compressed_image(f, image.get(), &infoheader); |
1119 | break; |
1120 | |
1121 | case BI_RLE4: |
1122 | read_rle4_compressed_image(f, image.get(), &infoheader); |
1123 | break; |
1124 | |
1125 | case BI_BITFIELDS: |
1126 | case BI_ALPHABITFIELDS: |
1127 | if (read_bitfields_image(f, image.get(), &infoheader, |
1128 | rmask, gmask, bmask, amask, withAlpha) < 0) { |
1129 | fop->setError("Unsupported bitfields in the BMP file.\n" ); |
1130 | return false; |
1131 | } |
1132 | break; |
1133 | |
1134 | default: |
1135 | fop->setError("Unsupported BMP compression.\n" ); |
1136 | return false; |
1137 | } |
1138 | |
1139 | if (ferror(f)) { |
1140 | fop->setError("Error reading file.\n" ); |
1141 | return false; |
1142 | } |
1143 | |
1144 | // Setup the file-data. |
1145 | if (!fop->formatOptions()) { |
1146 | auto bmp_options = std::make_shared<BmpOptions>(); |
1147 | |
1148 | bmp_options->format = format; |
1149 | bmp_options->compression = infoheader.biCompression; |
1150 | bmp_options->bits_per_pixel = infoheader.biBitCount; |
1151 | bmp_options->red_mask = rmask; |
1152 | bmp_options->green_mask = gmask; |
1153 | bmp_options->blue_mask = bmask; |
1154 | if (withAlpha) { |
1155 | bmp_options->alpha_mask = amask; |
1156 | fop->sequenceSetHasAlpha(true); |
1157 | } |
1158 | else |
1159 | bmp_options->alpha_mask = 0; |
1160 | fop->setLoadedFormatOptions(bmp_options); |
1161 | } |
1162 | |
1163 | return true; |
1164 | } |
1165 | |
1166 | #ifdef ENABLE_SAVE |
1167 | bool BmpFormat::onSave(FileOp *fop) |
1168 | { |
1169 | const FileAbstractImage* img = fop->abstractImage(); |
1170 | const ImageSpec spec = img->spec(); |
1171 | const int w = spec.width(); |
1172 | const int h = spec.height(); |
1173 | int bfSize; |
1174 | int biSizeImage; |
1175 | int ncolors = fop->sequenceGetNColors(); |
1176 | int bpp = 0; |
1177 | bool withAlpha = img->needAlpha(); |
1178 | |
1179 | switch (spec.colorMode()) { |
1180 | case ColorMode::RGB: |
1181 | if (withAlpha) |
1182 | bpp = 32; |
1183 | else |
1184 | bpp = 24; |
1185 | break; |
1186 | case ColorMode::GRAYSCALE: |
1187 | bpp = 8; |
1188 | break; |
1189 | case ColorMode::INDEXED: { |
1190 | if (ncolors > 16) |
1191 | bpp = 8; |
1192 | else if (ncolors > 2) |
1193 | bpp = 4; |
1194 | else |
1195 | bpp = 1; |
1196 | ncolors = (1 << bpp); |
1197 | break; |
1198 | } |
1199 | default: |
1200 | // TODO save ColorMode::BITMAP as 1bpp bmp? |
1201 | // Invalid image format |
1202 | fop->setError("Unsupported color mode.\n" ); |
1203 | return false; |
1204 | } |
1205 | |
1206 | int filler = int((32 - ((w*bpp-1) & 31)-1) / 8); |
1207 | int c, i, j, r, g, b; |
1208 | |
1209 | if (bpp <= 8) { |
1210 | biSizeImage = (w + filler)*bpp/8 * h; |
1211 | bfSize = (WININFOHEADERSIZE + OS2FILEHEADERSIZE // header |
1212 | + ncolors*4 // palette |
1213 | + biSizeImage); // image data |
1214 | } |
1215 | else { |
1216 | biSizeImage = (w*bpp/8 + filler) * h; |
1217 | if (withAlpha) |
1218 | bfSize = BV3INFOHEADERSIZE + |
1219 | OS2FILEHEADERSIZE + biSizeImage; // header + image data |
1220 | else |
1221 | bfSize = WININFOHEADERSIZE + |
1222 | OS2FILEHEADERSIZE + biSizeImage; // header + image data |
1223 | } |
1224 | |
1225 | FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb" )); |
1226 | FILE* f = handle.get(); |
1227 | |
1228 | /* file_header */ |
1229 | fputw(0x4D42, f); /* bfType ("BM") */ |
1230 | fputl(bfSize, f); /* bfSize */ |
1231 | fputw(0, f); /* bfReserved1 */ |
1232 | fputw(0, f); /* bfReserved2 */ |
1233 | |
1234 | if (bpp <= 8) { |
1235 | fputl(WININFOHEADERSIZE + OS2FILEHEADERSIZE + |
1236 | ncolors * 4, f); /* bfOffBits */ |
1237 | /* info_header */ |
1238 | fputl(WININFOHEADERSIZE, f); /* biSize */ |
1239 | } |
1240 | else if (withAlpha) { |
1241 | fputl(BV3INFOHEADERSIZE + OS2FILEHEADERSIZE, f); /* bfOffBits -taking account RBGA masks- */ |
1242 | /* info_header */ |
1243 | fputl(BV3INFOHEADERSIZE, f); /* biSize */ |
1244 | } |
1245 | else { |
1246 | fputl(WININFOHEADERSIZE + OS2FILEHEADERSIZE, f); /* bfOffBits */ |
1247 | /* info_header */ |
1248 | fputl(WININFOHEADERSIZE, f); /* biSize */ |
1249 | } |
1250 | |
1251 | fputl(w, f); /* biWidth */ |
1252 | fputl(h, f); /* biHeight */ |
1253 | fputw(1, f); /* biPlanes */ |
1254 | fputw(bpp, f); /* biBitCount */ |
1255 | if (withAlpha) /* biCompression */ |
1256 | fputl(BI_BITFIELDS, f); |
1257 | else |
1258 | fputl(BI_RGB, f); |
1259 | fputl(biSizeImage, f); /* biSizeImage */ |
1260 | fputl(0xB12, f); /* biXPelsPerMeter (0xB12 = 72 dpi) */ |
1261 | fputl(0xB12, f); /* biYPelsPerMeter */ |
1262 | |
1263 | if (bpp <= 8) { |
1264 | fputl(ncolors, f); /* biClrUsed */ |
1265 | fputl(ncolors, f); /* biClrImportant */ |
1266 | |
1267 | // Save the palette |
1268 | for (i=0; i<ncolors; i++) { |
1269 | fop->sequenceGetColor(i, &r, &g, &b); |
1270 | fputc(b, f); |
1271 | fputc(g, f); |
1272 | fputc(r, f); |
1273 | fputc(0, f); |
1274 | } |
1275 | } |
1276 | else { |
1277 | fputl(0, f); /* biClrUsed */ |
1278 | fputl(0, f); /* biClrImportant */ |
1279 | if (withAlpha) { |
1280 | fputl(0x00ff0000, f); |
1281 | fputl(0x0000ff00, f); |
1282 | fputl(0x000000ff, f); |
1283 | fputl(0xff000000, f); |
1284 | } |
1285 | } |
1286 | |
1287 | // Only used in indexed mode |
1288 | int colorsPerByte = std::max(1, 8/bpp); |
1289 | int colorMask; |
1290 | switch (bpp) { |
1291 | case 8: colorMask = 0xFF; break; |
1292 | case 4: colorMask = 0x0F; break; |
1293 | case 1: colorMask = 0x01; break; |
1294 | default: colorMask = 0; break; |
1295 | } |
1296 | |
1297 | // Save image pixels (from bottom to top) |
1298 | for (i=h-1; i>=0; i--) { |
1299 | switch (spec.colorMode()) { |
1300 | case ColorMode::RGB: { |
1301 | auto scanline = (const uint32_t*)img->getScanline(i); |
1302 | for (j=0; j<w; ++j) { |
1303 | c = scanline[j]; |
1304 | fputc(rgba_getb(c), f); |
1305 | fputc(rgba_getg(c), f); |
1306 | fputc(rgba_getr(c), f); |
1307 | if (withAlpha) |
1308 | fputc(rgba_geta(c), f); |
1309 | } |
1310 | break; |
1311 | } |
1312 | case ColorMode::GRAYSCALE: { |
1313 | auto scanline = (const uint16_t*)img->getScanline(i); |
1314 | for (j=0; j<w; ++j) { |
1315 | c = scanline[j]; |
1316 | fputc(graya_getv(c), f); |
1317 | } |
1318 | break; |
1319 | } |
1320 | case ColorMode::INDEXED: { |
1321 | auto scanline = (const uint8_t*)img->getScanline(i); |
1322 | for (j=0; j<w; ) { |
1323 | uint8_t value = 0; |
1324 | for (int k=colorsPerByte-1; k>=0 && j<w; --k, ++j) { |
1325 | c = scanline[j]; |
1326 | value |= (c & colorMask) << (bpp*k); |
1327 | } |
1328 | fputc(value, f); |
1329 | } |
1330 | break; |
1331 | } |
1332 | } |
1333 | |
1334 | for (j=0; j<filler; j++) |
1335 | fputc(0, f); |
1336 | |
1337 | fop->setProgress((float)(h-i) / (float)h); |
1338 | } |
1339 | |
1340 | if (ferror(f)) { |
1341 | fop->setError("Error writing file.\n" ); |
1342 | return false; |
1343 | } |
1344 | else { |
1345 | return true; |
1346 | } |
1347 | } |
1348 | #endif |
1349 | |
1350 | } // namespace app |
1351 | |