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