1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23/*
24 Code to load and save surfaces in Windows BMP format.
25
26 Why support BMP format? Well, it's a native format for Windows, and
27 most image processing programs can read and write it. It would be nice
28 to be able to have at least one image format that we can natively load
29 and save, and since PNG is so complex that it would bloat the library,
30 BMP is a good alternative.
31
32 This code currently supports Win32 DIBs in uncompressed 8 and 24 bpp.
33*/
34
35#include "SDL_pixels_c.h"
36#include "SDL_surface_c.h"
37
38#define SAVE_32BIT_BMP
39
40// Compression encodings for BMP files
41#ifndef BI_RGB
42#define BI_RGB 0
43#define BI_RLE8 1
44#define BI_RLE4 2
45#define BI_BITFIELDS 3
46#endif
47
48// Logical color space values for BMP files
49// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wmf/eb4bbd50-b3ce-4917-895c-be31f214797f
50#ifndef LCS_WINDOWS_COLOR_SPACE
51// 0x57696E20 == "Win "
52#define LCS_WINDOWS_COLOR_SPACE 0x57696E20
53#endif
54
55#ifndef LCS_sRGB
56// 0x73524742 == "sRGB"
57#define LCS_sRGB 0x73524742
58#endif
59
60// Logical/physical color relationship
61// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wmf/9fec0834-607d-427d-abd5-ab240fb0db38
62#ifndef LCS_GM_GRAPHICS
63#define LCS_GM_GRAPHICS 0x00000002
64#endif
65
66static bool readRlePixels(SDL_Surface *surface, SDL_IOStream *src, int isRle8)
67{
68 /*
69 | Sets the surface pixels from src. A bmp image is upside down.
70 */
71 int pitch = surface->pitch;
72 int height = surface->h;
73 Uint8 *start = (Uint8 *)surface->pixels;
74 Uint8 *end = start + (height * pitch);
75 Uint8 *bits = end - pitch, *spot;
76 int ofs = 0;
77 Uint8 ch;
78 Uint8 needsPad;
79 const int pixels_per_byte = (isRle8 ? 1 : 2);
80
81#define COPY_PIXEL(x) \
82 spot = &bits[ofs++]; \
83 if (spot >= start && spot < end) \
84 *spot = (x)
85
86 for (;;) {
87 if (!SDL_ReadU8(src, &ch)) {
88 return false;
89 }
90 /*
91 | encoded mode starts with a run length, and then a byte
92 | with two colour indexes to alternate between for the run
93 */
94 if (ch) {
95 Uint8 pixel;
96 if (!SDL_ReadU8(src, &pixel)) {
97 return false;
98 }
99 ch /= pixels_per_byte;
100 do {
101 COPY_PIXEL(pixel);
102 } while (--ch);
103 } else {
104 /*
105 | A leading zero is an escape; it may signal the end of the bitmap,
106 | a cursor move, or some absolute data.
107 | zero tag may be absolute mode or an escape
108 */
109 if (!SDL_ReadU8(src, &ch)) {
110 return false;
111 }
112 switch (ch) {
113 case 0: // end of line
114 ofs = 0;
115 bits -= pitch; // go to previous
116 break;
117 case 1: // end of bitmap
118 return true; // success!
119 case 2: // delta
120 if (!SDL_ReadU8(src, &ch)) {
121 return false;
122 }
123 ofs += ch / pixels_per_byte;
124
125 if (!SDL_ReadU8(src, &ch)) {
126 return false;
127 }
128 bits -= ((ch / pixels_per_byte) * pitch);
129 break;
130 default: // no compression
131 ch /= pixels_per_byte;
132 needsPad = (ch & 1);
133 do {
134 Uint8 pixel;
135 if (!SDL_ReadU8(src, &pixel)) {
136 return false;
137 }
138 COPY_PIXEL(pixel);
139 } while (--ch);
140
141 // pad at even boundary
142 if (needsPad && !SDL_ReadU8(src, &ch)) {
143 return false;
144 }
145 break;
146 }
147 }
148 }
149}
150
151static void CorrectAlphaChannel(SDL_Surface *surface)
152{
153 // Check to see if there is any alpha channel data
154 bool hasAlpha = false;
155#if SDL_BYTEORDER == SDL_BIG_ENDIAN
156 int alphaChannelOffset = 0;
157#else
158 int alphaChannelOffset = 3;
159#endif
160 Uint8 *alpha = ((Uint8 *)surface->pixels) + alphaChannelOffset;
161 Uint8 *end = alpha + surface->h * surface->pitch;
162
163 while (alpha < end) {
164 if (*alpha != 0) {
165 hasAlpha = true;
166 break;
167 }
168 alpha += 4;
169 }
170
171 if (!hasAlpha) {
172 alpha = ((Uint8 *)surface->pixels) + alphaChannelOffset;
173 while (alpha < end) {
174 *alpha = SDL_ALPHA_OPAQUE;
175 alpha += 4;
176 }
177 }
178}
179
180SDL_Surface *SDL_LoadBMP_IO(SDL_IOStream *src, bool closeio)
181{
182 bool was_error = true;
183 Sint64 fp_offset = 0;
184 int i, pad;
185 SDL_Surface *surface;
186 Uint32 Rmask = 0;
187 Uint32 Gmask = 0;
188 Uint32 Bmask = 0;
189 Uint32 Amask = 0;
190 Uint8 *bits;
191 Uint8 *top, *end;
192 bool topDown;
193 bool haveRGBMasks = false;
194 bool haveAlphaMask = false;
195 bool correctAlpha = false;
196
197 // The Win32 BMP file header (14 bytes)
198 char magic[2];
199 // Uint32 bfSize;
200 // Uint16 bfReserved1;
201 // Uint16 bfReserved2;
202 Uint32 bfOffBits;
203
204 // The Win32 BITMAPINFOHEADER struct (40 bytes)
205 Uint32 biSize;
206 Sint32 biWidth = 0;
207 Sint32 biHeight = 0;
208 // Uint16 biPlanes;
209 Uint16 biBitCount = 0;
210 Uint32 biCompression = 0;
211 // Uint32 biSizeImage;
212 // Sint32 biXPelsPerMeter;
213 // Sint32 biYPelsPerMeter;
214 Uint32 biClrUsed = 0;
215 // Uint32 biClrImportant;
216
217 // Make sure we are passed a valid data source
218 surface = NULL;
219 if (!src) {
220 SDL_InvalidParamError("src");
221 goto done;
222 }
223
224 // Read in the BMP file header
225 fp_offset = SDL_TellIO(src);
226 if (fp_offset < 0) {
227 goto done;
228 }
229 SDL_ClearError();
230 if (SDL_ReadIO(src, magic, 2) != 2) {
231 goto done;
232 }
233 if (SDL_strncmp(magic, "BM", 2) != 0) {
234 SDL_SetError("File is not a Windows BMP file");
235 goto done;
236 }
237 if (!SDL_ReadU32LE(src, NULL /* bfSize */) ||
238 !SDL_ReadU16LE(src, NULL /* bfReserved1 */) ||
239 !SDL_ReadU16LE(src, NULL /* bfReserved2 */) ||
240 !SDL_ReadU32LE(src, &bfOffBits)) {
241 goto done;
242 }
243
244 // Read the Win32 BITMAPINFOHEADER
245 if (!SDL_ReadU32LE(src, &biSize)) {
246 goto done;
247 }
248 if (biSize == 12) { // really old BITMAPCOREHEADER
249 Uint16 biWidth16, biHeight16;
250 if (!SDL_ReadU16LE(src, &biWidth16) ||
251 !SDL_ReadU16LE(src, &biHeight16) ||
252 !SDL_ReadU16LE(src, NULL /* biPlanes */) ||
253 !SDL_ReadU16LE(src, &biBitCount)) {
254 goto done;
255 }
256 biWidth = biWidth16;
257 biHeight = biHeight16;
258 biCompression = BI_RGB;
259 // biSizeImage = 0;
260 // biXPelsPerMeter = 0;
261 // biYPelsPerMeter = 0;
262 biClrUsed = 0;
263 // biClrImportant = 0;
264 } else if (biSize >= 40) { // some version of BITMAPINFOHEADER
265 Uint32 headerSize;
266 if (!SDL_ReadS32LE(src, &biWidth) ||
267 !SDL_ReadS32LE(src, &biHeight) ||
268 !SDL_ReadU16LE(src, NULL /* biPlanes */) ||
269 !SDL_ReadU16LE(src, &biBitCount) ||
270 !SDL_ReadU32LE(src, &biCompression) ||
271 !SDL_ReadU32LE(src, NULL /* biSizeImage */) ||
272 !SDL_ReadU32LE(src, NULL /* biXPelsPerMeter */) ||
273 !SDL_ReadU32LE(src, NULL /* biYPelsPerMeter */) ||
274 !SDL_ReadU32LE(src, &biClrUsed) ||
275 !SDL_ReadU32LE(src, NULL /* biClrImportant */)) {
276 goto done;
277 }
278
279 // 64 == BITMAPCOREHEADER2, an incompatible OS/2 2.x extension. Skip this stuff for now.
280 if (biSize != 64) {
281 /* This is complicated. If compression is BI_BITFIELDS, then
282 we have 3 DWORDS that specify the RGB masks. This is either
283 stored here in an BITMAPV2INFOHEADER (which only differs in
284 that it adds these RGB masks) and biSize >= 52, or we've got
285 these masks stored in the exact same place, but strictly
286 speaking, this is the bmiColors field in BITMAPINFO immediately
287 following the legacy v1 info header, just past biSize. */
288 if (biCompression == BI_BITFIELDS) {
289 haveRGBMasks = true;
290 if (!SDL_ReadU32LE(src, &Rmask) ||
291 !SDL_ReadU32LE(src, &Gmask) ||
292 !SDL_ReadU32LE(src, &Bmask)) {
293 goto done;
294 }
295
296 // ...v3 adds an alpha mask.
297 if (biSize >= 56) { // BITMAPV3INFOHEADER; adds alpha mask
298 haveAlphaMask = true;
299 if (!SDL_ReadU32LE(src, &Amask)) {
300 goto done;
301 }
302 }
303 } else {
304 // the mask fields are ignored for v2+ headers if not BI_BITFIELD.
305 if (biSize >= 52) { // BITMAPV2INFOHEADER; adds RGB masks
306 if (!SDL_ReadU32LE(src, NULL /* Rmask */) ||
307 !SDL_ReadU32LE(src, NULL /* Gmask */) ||
308 !SDL_ReadU32LE(src, NULL /* Bmask */)) {
309 goto done;
310 }
311 }
312 if (biSize >= 56) { // BITMAPV3INFOHEADER; adds alpha mask
313 if (!SDL_ReadU32LE(src, NULL /* Amask */)) {
314 goto done;
315 }
316 }
317 }
318
319 /* Insert other fields here; Wikipedia and MSDN say we're up to
320 v5 of this header, but we ignore those for now (they add gamma,
321 color spaces, etc). Ignoring the weird OS/2 2.x format, we
322 currently parse up to v3 correctly (hopefully!). */
323 }
324
325 // skip any header bytes we didn't handle...
326 headerSize = (Uint32)(SDL_TellIO(src) - (fp_offset + 14));
327 if (biSize > headerSize) {
328 if (SDL_SeekIO(src, (biSize - headerSize), SDL_IO_SEEK_CUR) < 0) {
329 goto done;
330 }
331 }
332 }
333 if (biWidth <= 0 || biHeight == 0) {
334 SDL_SetError("BMP file with bad dimensions (%" SDL_PRIs32 "x%" SDL_PRIs32 ")", biWidth, biHeight);
335 goto done;
336 }
337 if (biHeight < 0) {
338 topDown = true;
339 biHeight = -biHeight;
340 } else {
341 topDown = false;
342 }
343
344 // Check for read error
345 if (SDL_strcmp(SDL_GetError(), "") != 0) {
346 goto done;
347 }
348
349 // Reject invalid bit depths
350 switch (biBitCount) {
351 case 0:
352 case 3:
353 case 5:
354 case 6:
355 case 7:
356 SDL_SetError("%u bpp BMP images are not supported", biBitCount);
357 goto done;
358 default:
359 break;
360 }
361
362 // RLE4 and RLE8 BMP compression is supported
363 switch (biCompression) {
364 case BI_RGB:
365 // If there are no masks, use the defaults
366 SDL_assert(!haveRGBMasks);
367 SDL_assert(!haveAlphaMask);
368 // Default values for the BMP format
369 switch (biBitCount) {
370 case 15:
371 case 16:
372 // SDL_PIXELFORMAT_XRGB1555 or SDL_PIXELFORMAT_ARGB1555 if Amask
373 Rmask = 0x7C00;
374 Gmask = 0x03E0;
375 Bmask = 0x001F;
376 break;
377 case 24:
378#if SDL_BYTEORDER == SDL_BIG_ENDIAN
379 // SDL_PIXELFORMAT_RGB24
380 Rmask = 0x000000FF;
381 Gmask = 0x0000FF00;
382 Bmask = 0x00FF0000;
383#else
384 // SDL_PIXELFORMAT_BGR24
385 Rmask = 0x00FF0000;
386 Gmask = 0x0000FF00;
387 Bmask = 0x000000FF;
388#endif
389 break;
390 case 32:
391 // We don't know if this has alpha channel or not
392 correctAlpha = true;
393 // SDL_PIXELFORMAT_RGBA8888
394 Amask = 0xFF000000;
395 Rmask = 0x00FF0000;
396 Gmask = 0x0000FF00;
397 Bmask = 0x000000FF;
398 break;
399 default:
400 break;
401 }
402 break;
403
404 case BI_BITFIELDS:
405 break; // we handled this in the info header.
406
407 default:
408 break;
409 }
410
411 // Create a compatible surface, note that the colors are RGB ordered
412 {
413 SDL_PixelFormat format;
414
415 // Get the pixel format
416 format = SDL_GetPixelFormatForMasks(biBitCount, Rmask, Gmask, Bmask, Amask);
417 surface = SDL_CreateSurface(biWidth, biHeight, format);
418
419 if (!surface) {
420 goto done;
421 }
422 }
423
424 // Load the palette, if any
425 if (SDL_ISPIXELFORMAT_INDEXED(surface->format)) {
426 SDL_Palette *palette = SDL_CreateSurfacePalette(surface);
427 if (!palette) {
428 goto done;
429 }
430
431 if (SDL_SeekIO(src, fp_offset + 14 + biSize, SDL_IO_SEEK_SET) < 0) {
432 SDL_SetError("Error seeking in datastream");
433 goto done;
434 }
435
436 if (biBitCount >= 32) { // we shift biClrUsed by this value later.
437 SDL_SetError("Unsupported or incorrect biBitCount field");
438 goto done;
439 }
440
441 if (biClrUsed == 0) {
442 biClrUsed = 1 << biBitCount;
443 }
444
445 if (biClrUsed > (Uint32)palette->ncolors) {
446 biClrUsed = 1 << biBitCount; // try forcing it?
447 if (biClrUsed > (Uint32)palette->ncolors) {
448 SDL_SetError("Unsupported or incorrect biClrUsed field");
449 goto done;
450 }
451 }
452 palette->ncolors = biClrUsed;
453
454 if (biSize == 12) {
455 for (i = 0; i < palette->ncolors; ++i) {
456 if (!SDL_ReadU8(src, &palette->colors[i].b) ||
457 !SDL_ReadU8(src, &palette->colors[i].g) ||
458 !SDL_ReadU8(src, &palette->colors[i].r)) {
459 goto done;
460 }
461 palette->colors[i].a = SDL_ALPHA_OPAQUE;
462 }
463 } else {
464 for (i = 0; i < palette->ncolors; ++i) {
465 if (!SDL_ReadU8(src, &palette->colors[i].b) ||
466 !SDL_ReadU8(src, &palette->colors[i].g) ||
467 !SDL_ReadU8(src, &palette->colors[i].r) ||
468 !SDL_ReadU8(src, &palette->colors[i].a)) {
469 goto done;
470 }
471
472 /* According to Microsoft documentation, the fourth element
473 is reserved and must be zero, so we shouldn't treat it as
474 alpha.
475 */
476 palette->colors[i].a = SDL_ALPHA_OPAQUE;
477 }
478 }
479 }
480
481 // Read the surface pixels. Note that the bmp image is upside down
482 if (SDL_SeekIO(src, fp_offset + bfOffBits, SDL_IO_SEEK_SET) < 0) {
483 SDL_SetError("Error seeking in datastream");
484 goto done;
485 }
486 if ((biCompression == BI_RLE4) || (biCompression == BI_RLE8)) {
487 if (!readRlePixels(surface, src, biCompression == BI_RLE8)) {
488 SDL_SetError("Error reading from datastream");
489 goto done;
490 }
491
492 // Success!
493 was_error = false;
494 goto done;
495 }
496 top = (Uint8 *)surface->pixels;
497 end = (Uint8 *)surface->pixels + (surface->h * surface->pitch);
498 pad = ((surface->pitch % 4) ? (4 - (surface->pitch % 4)) : 0);
499 if (topDown) {
500 bits = top;
501 } else {
502 bits = end - surface->pitch;
503 }
504 while (bits >= top && bits < end) {
505 if (SDL_ReadIO(src, bits, surface->pitch) != (size_t)surface->pitch) {
506 goto done;
507 }
508 if (biBitCount == 8 && surface->palette && biClrUsed < (1u << biBitCount)) {
509 for (i = 0; i < surface->w; ++i) {
510 if (bits[i] >= biClrUsed) {
511 SDL_SetError("A BMP image contains a pixel with a color out of the palette");
512 goto done;
513 }
514 }
515 }
516#if SDL_BYTEORDER == SDL_BIG_ENDIAN
517 /* Byte-swap the pixels if needed. Note that the 24bpp
518 case has already been taken care of above. */
519 switch (biBitCount) {
520 case 15:
521 case 16:
522 {
523 Uint16 *pix = (Uint16 *)bits;
524 for (i = 0; i < surface->w; i++) {
525 pix[i] = SDL_Swap16(pix[i]);
526 }
527 break;
528 }
529
530 case 32:
531 {
532 Uint32 *pix = (Uint32 *)bits;
533 for (i = 0; i < surface->w; i++) {
534 pix[i] = SDL_Swap32(pix[i]);
535 }
536 break;
537 }
538 }
539#endif
540
541 // Skip padding bytes, ugh
542 if (pad) {
543 Uint8 padbyte;
544 for (i = 0; i < pad; ++i) {
545 if (!SDL_ReadU8(src, &padbyte)) {
546 goto done;
547 }
548 }
549 }
550 if (topDown) {
551 bits += surface->pitch;
552 } else {
553 bits -= surface->pitch;
554 }
555 }
556 if (correctAlpha) {
557 CorrectAlphaChannel(surface);
558 }
559
560 was_error = false;
561
562done:
563 if (was_error) {
564 if (src) {
565 SDL_SeekIO(src, fp_offset, SDL_IO_SEEK_SET);
566 }
567 SDL_DestroySurface(surface);
568 surface = NULL;
569 }
570 if (closeio && src) {
571 SDL_CloseIO(src);
572 }
573 return surface;
574}
575
576SDL_Surface *SDL_LoadBMP(const char *file)
577{
578 SDL_IOStream *stream = SDL_IOFromFile(file, "rb");
579 if (!stream) {
580 return NULL;
581 }
582 return SDL_LoadBMP_IO(stream, true);
583}
584
585bool SDL_SaveBMP_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
586{
587 bool was_error = true;
588 Sint64 fp_offset, new_offset;
589 int i, pad;
590 SDL_Surface *intermediate_surface = NULL;
591 Uint8 *bits;
592 bool save32bit = false;
593 bool saveLegacyBMP = false;
594
595 // The Win32 BMP file header (14 bytes)
596 char magic[2] = { 'B', 'M' };
597 Uint32 bfSize;
598 Uint16 bfReserved1;
599 Uint16 bfReserved2;
600 Uint32 bfOffBits;
601
602 // The Win32 BITMAPINFOHEADER struct (40 bytes)
603 Uint32 biSize;
604 Sint32 biWidth;
605 Sint32 biHeight;
606 Uint16 biPlanes;
607 Uint16 biBitCount;
608 Uint32 biCompression;
609 Uint32 biSizeImage;
610 Sint32 biXPelsPerMeter;
611 Sint32 biYPelsPerMeter;
612 Uint32 biClrUsed;
613 Uint32 biClrImportant;
614
615 // The additional header members from the Win32 BITMAPV4HEADER struct (108 bytes in total)
616 Uint32 bV4RedMask = 0;
617 Uint32 bV4GreenMask = 0;
618 Uint32 bV4BlueMask = 0;
619 Uint32 bV4AlphaMask = 0;
620 Uint32 bV4CSType = 0;
621 Sint32 bV4Endpoints[3 * 3] = { 0 };
622 Uint32 bV4GammaRed = 0;
623 Uint32 bV4GammaGreen = 0;
624 Uint32 bV4GammaBlue = 0;
625
626 // The additional header members from the Win32 BITMAPV5HEADER struct (124 bytes in total)
627 Uint32 bV5Intent = 0;
628 Uint32 bV5ProfileData = 0;
629 Uint32 bV5ProfileSize = 0;
630 Uint32 bV5Reserved = 0;
631
632 // Make sure we have somewhere to save
633 if (!SDL_SurfaceValid(surface)) {
634 SDL_InvalidParamError("surface");
635 goto done;
636 }
637 if (!dst) {
638 SDL_InvalidParamError("dst");
639 goto done;
640 }
641
642#ifdef SAVE_32BIT_BMP
643 // We can save alpha information in a 32-bit BMP
644 if (SDL_BITSPERPIXEL(surface->format) >= 8 &&
645 (SDL_ISPIXELFORMAT_ALPHA(surface->format) ||
646 surface->map.info.flags & SDL_COPY_COLORKEY)) {
647 save32bit = true;
648 }
649#endif // SAVE_32BIT_BMP
650
651 if (surface->palette && !save32bit) {
652 if (SDL_BITSPERPIXEL(surface->format) == 8) {
653 intermediate_surface = surface;
654 } else {
655 SDL_SetError("%u bpp BMP files not supported",
656 SDL_BITSPERPIXEL(surface->format));
657 goto done;
658 }
659 } else if ((surface->format == SDL_PIXELFORMAT_BGR24 && !save32bit) ||
660 (surface->format == SDL_PIXELFORMAT_BGRA32 && save32bit)) {
661 intermediate_surface = surface;
662 } else {
663 SDL_PixelFormat pixel_format;
664
665 /* If the surface has a colorkey or alpha channel we'll save a
666 32-bit BMP with alpha channel, otherwise save a 24-bit BMP. */
667 if (save32bit) {
668 pixel_format = SDL_PIXELFORMAT_BGRA32;
669 } else {
670 pixel_format = SDL_PIXELFORMAT_BGR24;
671 }
672 intermediate_surface = SDL_ConvertSurface(surface, pixel_format);
673 if (!intermediate_surface) {
674 SDL_SetError("Couldn't convert image to %d bpp",
675 (int)SDL_BITSPERPIXEL(pixel_format));
676 goto done;
677 }
678 }
679
680 if (save32bit) {
681 saveLegacyBMP = SDL_GetHintBoolean(SDL_HINT_BMP_SAVE_LEGACY_FORMAT, false);
682 }
683
684 if (SDL_LockSurface(intermediate_surface)) {
685 const size_t bw = intermediate_surface->w * intermediate_surface->fmt->bytes_per_pixel;
686
687 // Set the BMP file header values
688 bfSize = 0; // We'll write this when we're done
689 bfReserved1 = 0;
690 bfReserved2 = 0;
691 bfOffBits = 0; // We'll write this when we're done
692
693 // Write the BMP file header values
694 fp_offset = SDL_TellIO(dst);
695 if (fp_offset < 0) {
696 goto done;
697 }
698 if (SDL_WriteIO(dst, magic, 2) != 2 ||
699 !SDL_WriteU32LE(dst, bfSize) ||
700 !SDL_WriteU16LE(dst, bfReserved1) ||
701 !SDL_WriteU16LE(dst, bfReserved2) ||
702 !SDL_WriteU32LE(dst, bfOffBits)) {
703 goto done;
704 }
705
706 // Set the BMP info values
707 biSize = 40;
708 biWidth = intermediate_surface->w;
709 biHeight = intermediate_surface->h;
710 biPlanes = 1;
711 biBitCount = intermediate_surface->fmt->bits_per_pixel;
712 biCompression = BI_RGB;
713 biSizeImage = intermediate_surface->h * intermediate_surface->pitch;
714 biXPelsPerMeter = 0;
715 biYPelsPerMeter = 0;
716 if (intermediate_surface->palette) {
717 biClrUsed = intermediate_surface->palette->ncolors;
718 } else {
719 biClrUsed = 0;
720 }
721 biClrImportant = 0;
722
723 // Set the BMP info values
724 if (save32bit && !saveLegacyBMP) {
725 biSize = 124;
726 // Version 4 values
727 biCompression = BI_BITFIELDS;
728 // The BMP format is always little endian, these masks stay the same
729 bV4RedMask = 0x00ff0000;
730 bV4GreenMask = 0x0000ff00;
731 bV4BlueMask = 0x000000ff;
732 bV4AlphaMask = 0xff000000;
733 bV4CSType = LCS_sRGB;
734 bV4GammaRed = 0;
735 bV4GammaGreen = 0;
736 bV4GammaBlue = 0;
737 // Version 5 values
738 bV5Intent = LCS_GM_GRAPHICS;
739 bV5ProfileData = 0;
740 bV5ProfileSize = 0;
741 bV5Reserved = 0;
742 }
743
744 // Write the BMP info values
745 if (!SDL_WriteU32LE(dst, biSize) ||
746 !SDL_WriteS32LE(dst, biWidth) ||
747 !SDL_WriteS32LE(dst, biHeight) ||
748 !SDL_WriteU16LE(dst, biPlanes) ||
749 !SDL_WriteU16LE(dst, biBitCount) ||
750 !SDL_WriteU32LE(dst, biCompression) ||
751 !SDL_WriteU32LE(dst, biSizeImage) ||
752 !SDL_WriteU32LE(dst, biXPelsPerMeter) ||
753 !SDL_WriteU32LE(dst, biYPelsPerMeter) ||
754 !SDL_WriteU32LE(dst, biClrUsed) ||
755 !SDL_WriteU32LE(dst, biClrImportant)) {
756 goto done;
757 }
758
759 // Write the BMP info values
760 if (save32bit && !saveLegacyBMP) {
761 // Version 4 values
762 if (!SDL_WriteU32LE(dst, bV4RedMask) ||
763 !SDL_WriteU32LE(dst, bV4GreenMask) ||
764 !SDL_WriteU32LE(dst, bV4BlueMask) ||
765 !SDL_WriteU32LE(dst, bV4AlphaMask) ||
766 !SDL_WriteU32LE(dst, bV4CSType)) {
767 goto done;
768 }
769 for (i = 0; i < 3 * 3; i++) {
770 if (!SDL_WriteU32LE(dst, bV4Endpoints[i])) {
771 goto done;
772 }
773 }
774 if (!SDL_WriteU32LE(dst, bV4GammaRed) ||
775 !SDL_WriteU32LE(dst, bV4GammaGreen) ||
776 !SDL_WriteU32LE(dst, bV4GammaBlue)) {
777 goto done;
778 }
779 // Version 5 values
780 if (!SDL_WriteU32LE(dst, bV5Intent) ||
781 !SDL_WriteU32LE(dst, bV5ProfileData) ||
782 !SDL_WriteU32LE(dst, bV5ProfileSize) ||
783 !SDL_WriteU32LE(dst, bV5Reserved)) {
784 goto done;
785 }
786 }
787
788 // Write the palette (in BGR color order)
789 if (intermediate_surface->palette) {
790 SDL_Color *colors;
791 int ncolors;
792
793 colors = intermediate_surface->palette->colors;
794 ncolors = intermediate_surface->palette->ncolors;
795 for (i = 0; i < ncolors; ++i) {
796 if (!SDL_WriteU8(dst, colors[i].b) ||
797 !SDL_WriteU8(dst, colors[i].g) ||
798 !SDL_WriteU8(dst, colors[i].r) ||
799 !SDL_WriteU8(dst, colors[i].a)) {
800 goto done;
801 }
802 }
803 }
804
805 // Write the bitmap offset
806 bfOffBits = (Uint32)(SDL_TellIO(dst) - fp_offset);
807 if (SDL_SeekIO(dst, fp_offset + 10, SDL_IO_SEEK_SET) < 0) {
808 goto done;
809 }
810 if (!SDL_WriteU32LE(dst, bfOffBits)) {
811 goto done;
812 }
813 if (SDL_SeekIO(dst, fp_offset + bfOffBits, SDL_IO_SEEK_SET) < 0) {
814 goto done;
815 }
816
817 // Write the bitmap image upside down
818 bits = (Uint8 *)intermediate_surface->pixels + (intermediate_surface->h * intermediate_surface->pitch);
819 pad = ((bw % 4) ? (4 - (bw % 4)) : 0);
820 while (bits > (Uint8 *)intermediate_surface->pixels) {
821 bits -= intermediate_surface->pitch;
822 if (SDL_WriteIO(dst, bits, bw) != bw) {
823 goto done;
824 }
825 if (pad) {
826 const Uint8 padbyte = 0;
827 for (i = 0; i < pad; ++i) {
828 if (!SDL_WriteU8(dst, padbyte)) {
829 goto done;
830 }
831 }
832 }
833 }
834
835 // Write the BMP file size
836 new_offset = SDL_TellIO(dst);
837 if (new_offset < 0) {
838 goto done;
839 }
840 bfSize = (Uint32)(new_offset - fp_offset);
841 if (SDL_SeekIO(dst, fp_offset + 2, SDL_IO_SEEK_SET) < 0) {
842 goto done;
843 }
844 if (!SDL_WriteU32LE(dst, bfSize)) {
845 goto done;
846 }
847 if (SDL_SeekIO(dst, fp_offset + bfSize, SDL_IO_SEEK_SET) < 0) {
848 goto done;
849 }
850
851 // Close it up..
852 SDL_UnlockSurface(intermediate_surface);
853
854 was_error = false;
855 }
856
857done:
858 if (intermediate_surface && intermediate_surface != surface) {
859 SDL_DestroySurface(intermediate_surface);
860 }
861 if (closeio && dst) {
862 if (!SDL_CloseIO(dst)) {
863 was_error = true;
864 }
865 }
866 if (was_error) {
867 return false;
868 }
869 return true;
870}
871
872bool SDL_SaveBMP(SDL_Surface *surface, const char *file)
873{
874 SDL_IOStream *stream = SDL_IOFromFile(file, "wb");
875 if (!stream) {
876 return false;
877 }
878 return SDL_SaveBMP_IO(surface, stream, true);
879}
880