1 /*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2020 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_hints.h"
36 #include "SDL_video.h"
37 #include "SDL_assert.h"
38 #include "SDL_endian.h"
39 #include "SDL_pixels_c.h"
40
41 #define SAVE_32BIT_BMP
42
43 /* Compression encodings for BMP files */
44 #ifndef BI_RGB
45 #define BI_RGB 0
46 #define BI_RLE8 1
47 #define BI_RLE4 2
48 #define BI_BITFIELDS 3
49 #endif
50
51 /* Logical color space values for BMP files */
52 #ifndef LCS_WINDOWS_COLOR_SPACE
53 /* 0x57696E20 == "Win " */
54 #define LCS_WINDOWS_COLOR_SPACE 0x57696E20
55 #endif
56
readRlePixels(SDL_Surface * surface,SDL_RWops * src,int isRle8)57 static int readRlePixels(SDL_Surface * surface, SDL_RWops * src, int isRle8)
58 {
59 /*
60 | Sets the surface pixels from src. A bmp image is upside down.
61 */
62 int pitch = surface->pitch;
63 int height = surface->h;
64 Uint8 *start = (Uint8 *)surface->pixels;
65 Uint8 *end = start + (height*pitch);
66 Uint8 *bits = end-pitch, *spot;
67 int ofs = 0;
68 Uint8 ch;
69 Uint8 needsPad;
70
71 #define COPY_PIXEL(x) spot = &bits[ofs++]; if(spot >= start && spot < end) *spot = (x)
72
73 for (;;) {
74 if (!SDL_RWread(src, &ch, 1, 1)) return 1;
75 /*
76 | encoded mode starts with a run length, and then a byte
77 | with two colour indexes to alternate between for the run
78 */
79 if (ch) {
80 Uint8 pixel;
81 if (!SDL_RWread(src, &pixel, 1, 1)) return 1;
82 if (isRle8) { /* 256-color bitmap, compressed */
83 do {
84 COPY_PIXEL(pixel);
85 } while (--ch);
86 } else { /* 16-color bitmap, compressed */
87 Uint8 pixel0 = pixel >> 4;
88 Uint8 pixel1 = pixel & 0x0F;
89 for (;;) {
90 COPY_PIXEL(pixel0); /* even count, high nibble */
91 if (!--ch) break;
92 COPY_PIXEL(pixel1); /* odd count, low nibble */
93 if (!--ch) break;
94 }
95 }
96 } else {
97 /*
98 | A leading zero is an escape; it may signal the end of the bitmap,
99 | a cursor move, or some absolute data.
100 | zero tag may be absolute mode or an escape
101 */
102 if (!SDL_RWread(src, &ch, 1, 1)) return 1;
103 switch (ch) {
104 case 0: /* end of line */
105 ofs = 0;
106 bits -= pitch; /* go to previous */
107 break;
108 case 1: /* end of bitmap */
109 return 0; /* success! */
110 case 2: /* delta */
111 if (!SDL_RWread(src, &ch, 1, 1)) return 1;
112 ofs += ch;
113 if (!SDL_RWread(src, &ch, 1, 1)) return 1;
114 bits -= (ch * pitch);
115 break;
116 default: /* no compression */
117 if (isRle8) {
118 needsPad = (ch & 1);
119 do {
120 Uint8 pixel;
121 if (!SDL_RWread(src, &pixel, 1, 1)) return 1;
122 COPY_PIXEL(pixel);
123 } while (--ch);
124 } else {
125 needsPad = (((ch+1)>>1) & 1); /* (ch+1)>>1: bytes size */
126 for (;;) {
127 Uint8 pixel;
128 if (!SDL_RWread(src, &pixel, 1, 1)) return 1;
129 COPY_PIXEL(pixel >> 4);
130 if (!--ch) break;
131 COPY_PIXEL(pixel & 0x0F);
132 if (!--ch) break;
133 }
134 }
135 /* pad at even boundary */
136 if (needsPad && !SDL_RWread(src, &ch, 1, 1)) return 1;
137 break;
138 }
139 }
140 }
141 }
142
CorrectAlphaChannel(SDL_Surface * surface)143 static void CorrectAlphaChannel(SDL_Surface *surface)
144 {
145 /* Check to see if there is any alpha channel data */
146 SDL_bool hasAlpha = SDL_FALSE;
147 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
148 int alphaChannelOffset = 0;
149 #else
150 int alphaChannelOffset = 3;
151 #endif
152 Uint8 *alpha = ((Uint8*)surface->pixels) + alphaChannelOffset;
153 Uint8 *end = alpha + surface->h * surface->pitch;
154
155 while (alpha < end) {
156 if (*alpha != 0) {
157 hasAlpha = SDL_TRUE;
158 break;
159 }
160 alpha += 4;
161 }
162
163 if (!hasAlpha) {
164 alpha = ((Uint8*)surface->pixels) + alphaChannelOffset;
165 while (alpha < end) {
166 *alpha = SDL_ALPHA_OPAQUE;
167 alpha += 4;
168 }
169 }
170 }
171
172 SDL_Surface *
SDL_LoadBMP_RW(SDL_RWops * src,int freesrc)173 SDL_LoadBMP_RW(SDL_RWops * src, int freesrc)
174 {
175 SDL_bool was_error;
176 Sint64 fp_offset = 0;
177 int bmpPitch;
178 int i, pad;
179 SDL_Surface *surface;
180 Uint32 Rmask = 0;
181 Uint32 Gmask = 0;
182 Uint32 Bmask = 0;
183 Uint32 Amask = 0;
184 SDL_Palette *palette;
185 Uint8 *bits;
186 Uint8 *top, *end;
187 SDL_bool topDown;
188 int ExpandBMP;
189 SDL_bool haveRGBMasks = SDL_FALSE;
190 SDL_bool haveAlphaMask = SDL_FALSE;
191 SDL_bool correctAlpha = SDL_FALSE;
192
193 /* The Win32 BMP file header (14 bytes) */
194 char magic[2];
195 /* Uint32 bfSize; */
196 /* Uint16 bfReserved1; */
197 /* Uint16 bfReserved2; */
198 Uint32 bfOffBits;
199
200 /* The Win32 BITMAPINFOHEADER struct (40 bytes) */
201 Uint32 biSize;
202 Sint32 biWidth = 0;
203 Sint32 biHeight = 0;
204 /* Uint16 biPlanes; */
205 Uint16 biBitCount = 0;
206 Uint32 biCompression = 0;
207 /* Uint32 biSizeImage; */
208 /* Sint32 biXPelsPerMeter; */
209 /* Sint32 biYPelsPerMeter; */
210 Uint32 biClrUsed = 0;
211 /* Uint32 biClrImportant; */
212
213 /* Make sure we are passed a valid data source */
214 surface = NULL;
215 was_error = SDL_FALSE;
216 if (src == NULL) {
217 was_error = SDL_TRUE;
218 goto done;
219 }
220
221 /* Read in the BMP file header */
222 fp_offset = SDL_RWtell(src);
223 SDL_ClearError();
224 if (SDL_RWread(src, magic, 1, 2) != 2) {
225 SDL_Error(SDL_EFREAD);
226 was_error = SDL_TRUE;
227 goto done;
228 }
229 if (SDL_strncmp(magic, "BM", 2) != 0) {
230 SDL_SetError("File is not a Windows BMP file");
231 was_error = SDL_TRUE;
232 goto done;
233 }
234 /* bfSize = */ SDL_ReadLE32(src);
235 /* bfReserved1 = */ SDL_ReadLE16(src);
236 /* bfReserved2 = */ SDL_ReadLE16(src);
237 bfOffBits = SDL_ReadLE32(src);
238
239 /* Read the Win32 BITMAPINFOHEADER */
240 biSize = SDL_ReadLE32(src);
241 if (biSize == 12) { /* really old BITMAPCOREHEADER */
242 biWidth = (Uint32) SDL_ReadLE16(src);
243 biHeight = (Uint32) SDL_ReadLE16(src);
244 /* biPlanes = */ SDL_ReadLE16(src);
245 biBitCount = SDL_ReadLE16(src);
246 biCompression = BI_RGB;
247 /* biSizeImage = 0; */
248 /* biXPelsPerMeter = 0; */
249 /* biYPelsPerMeter = 0; */
250 biClrUsed = 0;
251 /* biClrImportant = 0; */
252 } else if (biSize >= 40) { /* some version of BITMAPINFOHEADER */
253 Uint32 headerSize;
254 biWidth = SDL_ReadLE32(src);
255 biHeight = SDL_ReadLE32(src);
256 /* biPlanes = */ SDL_ReadLE16(src);
257 biBitCount = SDL_ReadLE16(src);
258 biCompression = SDL_ReadLE32(src);
259 /* biSizeImage = */ SDL_ReadLE32(src);
260 /* biXPelsPerMeter = */ SDL_ReadLE32(src);
261 /* biYPelsPerMeter = */ SDL_ReadLE32(src);
262 biClrUsed = SDL_ReadLE32(src);
263 /* biClrImportant = */ SDL_ReadLE32(src);
264
265 /* 64 == BITMAPCOREHEADER2, an incompatible OS/2 2.x extension. Skip this stuff for now. */
266 if (biSize != 64) {
267 /* This is complicated. If compression is BI_BITFIELDS, then
268 we have 3 DWORDS that specify the RGB masks. This is either
269 stored here in an BITMAPV2INFOHEADER (which only differs in
270 that it adds these RGB masks) and biSize >= 52, or we've got
271 these masks stored in the exact same place, but strictly
272 speaking, this is the bmiColors field in BITMAPINFO immediately
273 following the legacy v1 info header, just past biSize. */
274 if (biCompression == BI_BITFIELDS) {
275 haveRGBMasks = SDL_TRUE;
276 Rmask = SDL_ReadLE32(src);
277 Gmask = SDL_ReadLE32(src);
278 Bmask = SDL_ReadLE32(src);
279
280 /* ...v3 adds an alpha mask. */
281 if (biSize >= 56) { /* BITMAPV3INFOHEADER; adds alpha mask */
282 haveAlphaMask = SDL_TRUE;
283 Amask = SDL_ReadLE32(src);
284 }
285 } else {
286 /* the mask fields are ignored for v2+ headers if not BI_BITFIELD. */
287 if (biSize >= 52) { /* BITMAPV2INFOHEADER; adds RGB masks */
288 /*Rmask = */ SDL_ReadLE32(src);
289 /*Gmask = */ SDL_ReadLE32(src);
290 /*Bmask = */ SDL_ReadLE32(src);
291 }
292 if (biSize >= 56) { /* BITMAPV3INFOHEADER; adds alpha mask */
293 /*Amask = */ SDL_ReadLE32(src);
294 }
295 }
296
297 /* Insert other fields here; Wikipedia and MSDN say we're up to
298 v5 of this header, but we ignore those for now (they add gamma,
299 color spaces, etc). Ignoring the weird OS/2 2.x format, we
300 currently parse up to v3 correctly (hopefully!). */
301 }
302
303 /* skip any header bytes we didn't handle... */
304 headerSize = (Uint32) (SDL_RWtell(src) - (fp_offset + 14));
305 if (biSize > headerSize) {
306 SDL_RWseek(src, (biSize - headerSize), RW_SEEK_CUR);
307 }
308 }
309 if (biWidth <= 0 || biHeight == 0) {
310 SDL_SetError("BMP file with bad dimensions (%dx%d)", biWidth, biHeight);
311 was_error = SDL_TRUE;
312 goto done;
313 }
314 if (biHeight < 0) {
315 topDown = SDL_TRUE;
316 biHeight = -biHeight;
317 } else {
318 topDown = SDL_FALSE;
319 }
320
321 /* Check for read error */
322 if (SDL_strcmp(SDL_GetError(), "") != 0) {
323 was_error = SDL_TRUE;
324 goto done;
325 }
326
327 /* Expand 1 and 4 bit bitmaps to 8 bits per pixel */
328 switch (biBitCount) {
329 case 1:
330 case 4:
331 ExpandBMP = biBitCount;
332 biBitCount = 8;
333 break;
334 case 0:
335 case 2:
336 case 3:
337 case 5:
338 case 6:
339 case 7:
340 SDL_SetError("%d-bpp BMP images are not supported", biBitCount);
341 was_error = SDL_TRUE;
342 goto done;
343 default:
344 ExpandBMP = 0;
345 break;
346 }
347
348 /* RLE4 and RLE8 BMP compression is supported */
349 switch (biCompression) {
350 case BI_RGB:
351 /* If there are no masks, use the defaults */
352 SDL_assert(!haveRGBMasks);
353 SDL_assert(!haveAlphaMask);
354 /* Default values for the BMP format */
355 switch (biBitCount) {
356 case 15:
357 case 16:
358 Rmask = 0x7C00;
359 Gmask = 0x03E0;
360 Bmask = 0x001F;
361 break;
362 case 24:
363 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
364 Rmask = 0x000000FF;
365 Gmask = 0x0000FF00;
366 Bmask = 0x00FF0000;
367 #else
368 Rmask = 0x00FF0000;
369 Gmask = 0x0000FF00;
370 Bmask = 0x000000FF;
371 #endif
372 break;
373 case 32:
374 /* We don't know if this has alpha channel or not */
375 correctAlpha = SDL_TRUE;
376 Amask = 0xFF000000;
377 Rmask = 0x00FF0000;
378 Gmask = 0x0000FF00;
379 Bmask = 0x000000FF;
380 break;
381 default:
382 break;
383 }
384 break;
385
386 case BI_BITFIELDS:
387 break; /* we handled this in the info header. */
388
389 default:
390 break;
391 }
392
393 /* Create a compatible surface, note that the colors are RGB ordered */
394 surface =
395 SDL_CreateRGBSurface(0, biWidth, biHeight, biBitCount, Rmask, Gmask,
396 Bmask, Amask);
397 if (surface == NULL) {
398 was_error = SDL_TRUE;
399 goto done;
400 }
401
402 /* Load the palette, if any */
403 palette = (surface->format)->palette;
404 if (palette) {
405 if (SDL_RWseek(src, fp_offset+14+biSize, RW_SEEK_SET) < 0) {
406 SDL_Error(SDL_EFSEEK);
407 was_error = SDL_TRUE;
408 goto done;
409 }
410
411 /*
412 | guich: always use 1<<bpp b/c some bitmaps can bring wrong information
413 | for colorsUsed
414 */
415 /* if (biClrUsed == 0) { */
416 biClrUsed = 1 << biBitCount;
417 /* } */
418 if (biSize == 12) {
419 for (i = 0; i < (int) biClrUsed; ++i) {
420 SDL_RWread(src, &palette->colors[i].b, 1, 1);
421 SDL_RWread(src, &palette->colors[i].g, 1, 1);
422 SDL_RWread(src, &palette->colors[i].r, 1, 1);
423 palette->colors[i].a = SDL_ALPHA_OPAQUE;
424 }
425 } else {
426 for (i = 0; i < (int) biClrUsed; ++i) {
427 SDL_RWread(src, &palette->colors[i].b, 1, 1);
428 SDL_RWread(src, &palette->colors[i].g, 1, 1);
429 SDL_RWread(src, &palette->colors[i].r, 1, 1);
430 SDL_RWread(src, &palette->colors[i].a, 1, 1);
431
432 /* According to Microsoft documentation, the fourth element
433 is reserved and must be zero, so we shouldn't treat it as
434 alpha.
435 */
436 palette->colors[i].a = SDL_ALPHA_OPAQUE;
437 }
438 }
439 palette->ncolors = biClrUsed;
440 }
441
442 /* Read the surface pixels. Note that the bmp image is upside down */
443 if (SDL_RWseek(src, fp_offset + bfOffBits, RW_SEEK_SET) < 0) {
444 SDL_Error(SDL_EFSEEK);
445 was_error = SDL_TRUE;
446 goto done;
447 }
448 if ((biCompression == BI_RLE4) || (biCompression == BI_RLE8)) {
449 was_error = (SDL_bool)readRlePixels(surface, src, biCompression == BI_RLE8);
450 if (was_error) SDL_SetError("Error reading from BMP");
451 goto done;
452 }
453 top = (Uint8 *)surface->pixels;
454 end = (Uint8 *)surface->pixels+(surface->h*surface->pitch);
455 switch (ExpandBMP) {
456 case 1:
457 bmpPitch = (biWidth + 7) >> 3;
458 pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0);
459 break;
460 case 4:
461 bmpPitch = (biWidth + 1) >> 1;
462 pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0);
463 break;
464 default:
465 pad = ((surface->pitch % 4) ? (4 - (surface->pitch % 4)) : 0);
466 break;
467 }
468 if (topDown) {
469 bits = top;
470 } else {
471 bits = end - surface->pitch;
472 }
473 while (bits >= top && bits < end) {
474 switch (ExpandBMP) {
475 case 1:
476 case 4:{
477 Uint8 pixel = 0;
478 int shift = (8 - ExpandBMP);
479 for (i = 0; i < surface->w; ++i) {
480 if (i % (8 / ExpandBMP) == 0) {
481 if (!SDL_RWread(src, &pixel, 1, 1)) {
482 SDL_SetError("Error reading from BMP");
483 was_error = SDL_TRUE;
484 goto done;
485 }
486 }
487 bits[i] = (pixel >> shift);
488 if (bits[i] >= biClrUsed) {
489 SDL_SetError("A BMP image contains a pixel with a color out of the palette");
490 was_error = SDL_TRUE;
491 goto done;
492 }
493 pixel <<= ExpandBMP;
494 }
495 }
496 break;
497
498 default:
499 if (SDL_RWread(src, bits, 1, surface->pitch) != surface->pitch) {
500 SDL_Error(SDL_EFREAD);
501 was_error = SDL_TRUE;
502 goto done;
503 }
504 if (biBitCount == 8 && palette && biClrUsed < (1u << biBitCount)) {
505 for (i = 0; i < surface->w; ++i) {
506 if (bits[i] >= biClrUsed) {
507 SDL_SetError("A BMP image contains a pixel with a color out of the palette");
508 was_error = SDL_TRUE;
509 goto done;
510 }
511 }
512 }
513 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
514 /* Byte-swap the pixels if needed. Note that the 24bpp
515 case has already been taken care of above. */
516 switch (biBitCount) {
517 case 15:
518 case 16:{
519 Uint16 *pix = (Uint16 *) bits;
520 for (i = 0; i < surface->w; i++)
521 pix[i] = SDL_Swap16(pix[i]);
522 break;
523 }
524
525 case 32:{
526 Uint32 *pix = (Uint32 *) bits;
527 for (i = 0; i < surface->w; i++)
528 pix[i] = SDL_Swap32(pix[i]);
529 break;
530 }
531 }
532 #endif
533 break;
534 }
535 /* Skip padding bytes, ugh */
536 if (pad) {
537 Uint8 padbyte;
538 for (i = 0; i < pad; ++i) {
539 SDL_RWread(src, &padbyte, 1, 1);
540 }
541 }
542 if (topDown) {
543 bits += surface->pitch;
544 } else {
545 bits -= surface->pitch;
546 }
547 }
548 if (correctAlpha) {
549 CorrectAlphaChannel(surface);
550 }
551 done:
552 if (was_error) {
553 if (src) {
554 SDL_RWseek(src, fp_offset, RW_SEEK_SET);
555 }
556 if (surface) {
557 SDL_FreeSurface(surface);
558 }
559 surface = NULL;
560 }
561 if (freesrc && src) {
562 SDL_RWclose(src);
563 }
564 return (surface);
565 }
566
567 int
SDL_SaveBMP_RW(SDL_Surface * saveme,SDL_RWops * dst,int freedst)568 SDL_SaveBMP_RW(SDL_Surface * saveme, SDL_RWops * dst, int freedst)
569 {
570 Sint64 fp_offset;
571 int i, pad;
572 SDL_Surface *surface;
573 Uint8 *bits;
574 SDL_bool save32bit = SDL_FALSE;
575 SDL_bool saveLegacyBMP = SDL_FALSE;
576
577 /* The Win32 BMP file header (14 bytes) */
578 char magic[2] = { 'B', 'M' };
579 Uint32 bfSize;
580 Uint16 bfReserved1;
581 Uint16 bfReserved2;
582 Uint32 bfOffBits;
583
584 /* The Win32 BITMAPINFOHEADER struct (40 bytes) */
585 Uint32 biSize;
586 Sint32 biWidth;
587 Sint32 biHeight;
588 Uint16 biPlanes;
589 Uint16 biBitCount;
590 Uint32 biCompression;
591 Uint32 biSizeImage;
592 Sint32 biXPelsPerMeter;
593 Sint32 biYPelsPerMeter;
594 Uint32 biClrUsed;
595 Uint32 biClrImportant;
596
597 /* The additional header members from the Win32 BITMAPV4HEADER struct (108 bytes in total) */
598 Uint32 bV4RedMask = 0;
599 Uint32 bV4GreenMask = 0;
600 Uint32 bV4BlueMask = 0;
601 Uint32 bV4AlphaMask = 0;
602 Uint32 bV4CSType = 0;
603 Sint32 bV4Endpoints[3 * 3] = {0};
604 Uint32 bV4GammaRed = 0;
605 Uint32 bV4GammaGreen = 0;
606 Uint32 bV4GammaBlue = 0;
607
608 /* Make sure we have somewhere to save */
609 surface = NULL;
610 if (dst) {
611 #ifdef SAVE_32BIT_BMP
612 /* We can save alpha information in a 32-bit BMP */
613 if (saveme->format->BitsPerPixel >= 8 && (saveme->format->Amask ||
614 saveme->map->info.flags & SDL_COPY_COLORKEY)) {
615 save32bit = SDL_TRUE;
616 }
617 #endif /* SAVE_32BIT_BMP */
618
619 if (saveme->format->palette && !save32bit) {
620 if (saveme->format->BitsPerPixel == 8) {
621 surface = saveme;
622 } else {
623 SDL_SetError("%d bpp BMP files not supported",
624 saveme->format->BitsPerPixel);
625 }
626 } else if ((saveme->format->BitsPerPixel == 24) && !save32bit &&
627 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
628 (saveme->format->Rmask == 0x00FF0000) &&
629 (saveme->format->Gmask == 0x0000FF00) &&
630 (saveme->format->Bmask == 0x000000FF)
631 #else
632 (saveme->format->Rmask == 0x000000FF) &&
633 (saveme->format->Gmask == 0x0000FF00) &&
634 (saveme->format->Bmask == 0x00FF0000)
635 #endif
636 ) {
637 surface = saveme;
638 } else {
639 SDL_PixelFormat format;
640
641 /* If the surface has a colorkey or alpha channel we'll save a
642 32-bit BMP with alpha channel, otherwise save a 24-bit BMP. */
643 if (save32bit) {
644 SDL_InitFormat(&format, SDL_PIXELFORMAT_BGRA32);
645 } else {
646 SDL_InitFormat(&format, SDL_PIXELFORMAT_BGR24);
647 }
648 surface = SDL_ConvertSurface(saveme, &format, 0);
649 if (!surface) {
650 SDL_SetError("Couldn't convert image to %d bpp",
651 format.BitsPerPixel);
652 }
653 }
654 } else {
655 /* Set no error here because it may overwrite a more useful message from
656 SDL_RWFromFile() if SDL_SaveBMP_RW() is called from SDL_SaveBMP(). */
657 return -1;
658 }
659
660 if (save32bit) {
661 saveLegacyBMP = SDL_GetHintBoolean(SDL_HINT_BMP_SAVE_LEGACY_FORMAT, SDL_FALSE);
662 }
663
664 if (surface && (SDL_LockSurface(surface) == 0)) {
665 const int bw = surface->w * surface->format->BytesPerPixel;
666
667 /* Set the BMP file header values */
668 bfSize = 0; /* We'll write this when we're done */
669 bfReserved1 = 0;
670 bfReserved2 = 0;
671 bfOffBits = 0; /* We'll write this when we're done */
672
673 /* Write the BMP file header values */
674 fp_offset = SDL_RWtell(dst);
675 SDL_ClearError();
676 SDL_RWwrite(dst, magic, 2, 1);
677 SDL_WriteLE32(dst, bfSize);
678 SDL_WriteLE16(dst, bfReserved1);
679 SDL_WriteLE16(dst, bfReserved2);
680 SDL_WriteLE32(dst, bfOffBits);
681
682 /* Set the BMP info values */
683 biSize = 40;
684 biWidth = surface->w;
685 biHeight = surface->h;
686 biPlanes = 1;
687 biBitCount = surface->format->BitsPerPixel;
688 biCompression = BI_RGB;
689 biSizeImage = surface->h * surface->pitch;
690 biXPelsPerMeter = 0;
691 biYPelsPerMeter = 0;
692 if (surface->format->palette) {
693 biClrUsed = surface->format->palette->ncolors;
694 } else {
695 biClrUsed = 0;
696 }
697 biClrImportant = 0;
698
699 /* Set the BMP info values for the version 4 header */
700 if (save32bit && !saveLegacyBMP) {
701 biSize = 108;
702 biCompression = BI_BITFIELDS;
703 /* The BMP format is always little endian, these masks stay the same */
704 bV4RedMask = 0x00ff0000;
705 bV4GreenMask = 0x0000ff00;
706 bV4BlueMask = 0x000000ff;
707 bV4AlphaMask = 0xff000000;
708 bV4CSType = LCS_WINDOWS_COLOR_SPACE;
709 bV4GammaRed = 0;
710 bV4GammaGreen = 0;
711 bV4GammaBlue = 0;
712 }
713
714 /* Write the BMP info values */
715 SDL_WriteLE32(dst, biSize);
716 SDL_WriteLE32(dst, biWidth);
717 SDL_WriteLE32(dst, biHeight);
718 SDL_WriteLE16(dst, biPlanes);
719 SDL_WriteLE16(dst, biBitCount);
720 SDL_WriteLE32(dst, biCompression);
721 SDL_WriteLE32(dst, biSizeImage);
722 SDL_WriteLE32(dst, biXPelsPerMeter);
723 SDL_WriteLE32(dst, biYPelsPerMeter);
724 SDL_WriteLE32(dst, biClrUsed);
725 SDL_WriteLE32(dst, biClrImportant);
726
727 /* Write the BMP info values for the version 4 header */
728 if (save32bit && !saveLegacyBMP) {
729 SDL_WriteLE32(dst, bV4RedMask);
730 SDL_WriteLE32(dst, bV4GreenMask);
731 SDL_WriteLE32(dst, bV4BlueMask);
732 SDL_WriteLE32(dst, bV4AlphaMask);
733 SDL_WriteLE32(dst, bV4CSType);
734 for (i = 0; i < 3 * 3; i++) {
735 SDL_WriteLE32(dst, bV4Endpoints[i]);
736 }
737 SDL_WriteLE32(dst, bV4GammaRed);
738 SDL_WriteLE32(dst, bV4GammaGreen);
739 SDL_WriteLE32(dst, bV4GammaBlue);
740 }
741
742 /* Write the palette (in BGR color order) */
743 if (surface->format->palette) {
744 SDL_Color *colors;
745 int ncolors;
746
747 colors = surface->format->palette->colors;
748 ncolors = surface->format->palette->ncolors;
749 for (i = 0; i < ncolors; ++i) {
750 SDL_RWwrite(dst, &colors[i].b, 1, 1);
751 SDL_RWwrite(dst, &colors[i].g, 1, 1);
752 SDL_RWwrite(dst, &colors[i].r, 1, 1);
753 SDL_RWwrite(dst, &colors[i].a, 1, 1);
754 }
755 }
756
757 /* Write the bitmap offset */
758 bfOffBits = (Uint32)(SDL_RWtell(dst) - fp_offset);
759 if (SDL_RWseek(dst, fp_offset + 10, RW_SEEK_SET) < 0) {
760 SDL_Error(SDL_EFSEEK);
761 }
762 SDL_WriteLE32(dst, bfOffBits);
763 if (SDL_RWseek(dst, fp_offset + bfOffBits, RW_SEEK_SET) < 0) {
764 SDL_Error(SDL_EFSEEK);
765 }
766
767 /* Write the bitmap image upside down */
768 bits = (Uint8 *) surface->pixels + (surface->h * surface->pitch);
769 pad = ((bw % 4) ? (4 - (bw % 4)) : 0);
770 while (bits > (Uint8 *) surface->pixels) {
771 bits -= surface->pitch;
772 if (SDL_RWwrite(dst, bits, 1, bw) != bw) {
773 SDL_Error(SDL_EFWRITE);
774 break;
775 }
776 if (pad) {
777 const Uint8 padbyte = 0;
778 for (i = 0; i < pad; ++i) {
779 SDL_RWwrite(dst, &padbyte, 1, 1);
780 }
781 }
782 }
783
784 /* Write the BMP file size */
785 bfSize = (Uint32)(SDL_RWtell(dst) - fp_offset);
786 if (SDL_RWseek(dst, fp_offset + 2, RW_SEEK_SET) < 0) {
787 SDL_Error(SDL_EFSEEK);
788 }
789 SDL_WriteLE32(dst, bfSize);
790 if (SDL_RWseek(dst, fp_offset + bfSize, RW_SEEK_SET) < 0) {
791 SDL_Error(SDL_EFSEEK);
792 }
793
794 /* Close it up.. */
795 SDL_UnlockSurface(surface);
796 if (surface != saveme) {
797 SDL_FreeSurface(surface);
798 }
799 }
800
801 if (freedst && dst) {
802 SDL_RWclose(dst);
803 }
804 return ((SDL_strcmp(SDL_GetError(), "") == 0) ? 0 : -1);
805 }
806
807 /* vi: set ts=4 sw=4 expandtab: */
808