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
56static 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
142static 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
171SDL_Surface *
172SDL_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 /*
411 | guich: always use 1<<bpp b/c some bitmaps can bring wrong information
412 | for colorsUsed
413 */
414 /* if (biClrUsed == 0) { */
415 biClrUsed = 1 << biBitCount;
416 /* } */
417 if (biSize == 12) {
418 for (i = 0; i < (int) biClrUsed; ++i) {
419 SDL_RWread(src, &palette->colors[i].b, 1, 1);
420 SDL_RWread(src, &palette->colors[i].g, 1, 1);
421 SDL_RWread(src, &palette->colors[i].r, 1, 1);
422 palette->colors[i].a = SDL_ALPHA_OPAQUE;
423 }
424 } else {
425 for (i = 0; i < (int) biClrUsed; ++i) {
426 SDL_RWread(src, &palette->colors[i].b, 1, 1);
427 SDL_RWread(src, &palette->colors[i].g, 1, 1);
428 SDL_RWread(src, &palette->colors[i].r, 1, 1);
429 SDL_RWread(src, &palette->colors[i].a, 1, 1);
430
431 /* According to Microsoft documentation, the fourth element
432 is reserved and must be zero, so we shouldn't treat it as
433 alpha.
434 */
435 palette->colors[i].a = SDL_ALPHA_OPAQUE;
436 }
437 }
438 palette->ncolors = biClrUsed;
439 }
440
441 /* Read the surface pixels. Note that the bmp image is upside down */
442 if (SDL_RWseek(src, fp_offset + bfOffBits, RW_SEEK_SET) < 0) {
443 SDL_Error(SDL_EFSEEK);
444 was_error = SDL_TRUE;
445 goto done;
446 }
447 if ((biCompression == BI_RLE4) || (biCompression == BI_RLE8)) {
448 was_error = (SDL_bool)readRlePixels(surface, src, biCompression == BI_RLE8);
449 if (was_error) SDL_SetError("Error reading from BMP");
450 goto done;
451 }
452 top = (Uint8 *)surface->pixels;
453 end = (Uint8 *)surface->pixels+(surface->h*surface->pitch);
454 switch (ExpandBMP) {
455 case 1:
456 bmpPitch = (biWidth + 7) >> 3;
457 pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0);
458 break;
459 case 4:
460 bmpPitch = (biWidth + 1) >> 1;
461 pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0);
462 break;
463 default:
464 pad = ((surface->pitch % 4) ? (4 - (surface->pitch % 4)) : 0);
465 break;
466 }
467 if (topDown) {
468 bits = top;
469 } else {
470 bits = end - surface->pitch;
471 }
472 while (bits >= top && bits < end) {
473 switch (ExpandBMP) {
474 case 1:
475 case 4:{
476 Uint8 pixel = 0;
477 int shift = (8 - ExpandBMP);
478 for (i = 0; i < surface->w; ++i) {
479 if (i % (8 / ExpandBMP) == 0) {
480 if (!SDL_RWread(src, &pixel, 1, 1)) {
481 SDL_SetError("Error reading from BMP");
482 was_error = SDL_TRUE;
483 goto done;
484 }
485 }
486 bits[i] = (pixel >> shift);
487 if (bits[i] >= biClrUsed) {
488 SDL_SetError("A BMP image contains a pixel with a color out of the palette");
489 was_error = SDL_TRUE;
490 goto done;
491 }
492 pixel <<= ExpandBMP;
493 }
494 }
495 break;
496
497 default:
498 if (SDL_RWread(src, bits, 1, surface->pitch) != surface->pitch) {
499 SDL_Error(SDL_EFREAD);
500 was_error = SDL_TRUE;
501 goto done;
502 }
503 if (biBitCount == 8 && palette && biClrUsed < (1u << biBitCount)) {
504 for (i = 0; i < surface->w; ++i) {
505 if (bits[i] >= biClrUsed) {
506 SDL_SetError("A BMP image contains a pixel with a color out of the palette");
507 was_error = SDL_TRUE;
508 goto done;
509 }
510 }
511 }
512#if SDL_BYTEORDER == SDL_BIG_ENDIAN
513 /* Byte-swap the pixels if needed. Note that the 24bpp
514 case has already been taken care of above. */
515 switch (biBitCount) {
516 case 15:
517 case 16:{
518 Uint16 *pix = (Uint16 *) bits;
519 for (i = 0; i < surface->w; i++)
520 pix[i] = SDL_Swap16(pix[i]);
521 break;
522 }
523
524 case 32:{
525 Uint32 *pix = (Uint32 *) bits;
526 for (i = 0; i < surface->w; i++)
527 pix[i] = SDL_Swap32(pix[i]);
528 break;
529 }
530 }
531#endif
532 break;
533 }
534 /* Skip padding bytes, ugh */
535 if (pad) {
536 Uint8 padbyte;
537 for (i = 0; i < pad; ++i) {
538 SDL_RWread(src, &padbyte, 1, 1);
539 }
540 }
541 if (topDown) {
542 bits += surface->pitch;
543 } else {
544 bits -= surface->pitch;
545 }
546 }
547 if (correctAlpha) {
548 CorrectAlphaChannel(surface);
549 }
550 done:
551 if (was_error) {
552 if (src) {
553 SDL_RWseek(src, fp_offset, RW_SEEK_SET);
554 }
555 if (surface) {
556 SDL_FreeSurface(surface);
557 }
558 surface = NULL;
559 }
560 if (freesrc && src) {
561 SDL_RWclose(src);
562 }
563 return (surface);
564}
565
566int
567SDL_SaveBMP_RW(SDL_Surface * saveme, SDL_RWops * dst, int freedst)
568{
569 Sint64 fp_offset;
570 int i, pad;
571 SDL_Surface *surface;
572 Uint8 *bits;
573 SDL_bool save32bit = SDL_FALSE;
574 SDL_bool saveLegacyBMP = SDL_FALSE;
575
576 /* The Win32 BMP file header (14 bytes) */
577 char magic[2] = { 'B', 'M' };
578 Uint32 bfSize;
579 Uint16 bfReserved1;
580 Uint16 bfReserved2;
581 Uint32 bfOffBits;
582
583 /* The Win32 BITMAPINFOHEADER struct (40 bytes) */
584 Uint32 biSize;
585 Sint32 biWidth;
586 Sint32 biHeight;
587 Uint16 biPlanes;
588 Uint16 biBitCount;
589 Uint32 biCompression;
590 Uint32 biSizeImage;
591 Sint32 biXPelsPerMeter;
592 Sint32 biYPelsPerMeter;
593 Uint32 biClrUsed;
594 Uint32 biClrImportant;
595
596 /* The additional header members from the Win32 BITMAPV4HEADER struct (108 bytes in total) */
597 Uint32 bV4RedMask = 0;
598 Uint32 bV4GreenMask = 0;
599 Uint32 bV4BlueMask = 0;
600 Uint32 bV4AlphaMask = 0;
601 Uint32 bV4CSType = 0;
602 Sint32 bV4Endpoints[3 * 3] = {0};
603 Uint32 bV4GammaRed = 0;
604 Uint32 bV4GammaGreen = 0;
605 Uint32 bV4GammaBlue = 0;
606
607 /* Make sure we have somewhere to save */
608 surface = NULL;
609 if (dst) {
610#ifdef SAVE_32BIT_BMP
611 /* We can save alpha information in a 32-bit BMP */
612 if (saveme->format->BitsPerPixel >= 8 && (saveme->format->Amask ||
613 saveme->map->info.flags & SDL_COPY_COLORKEY)) {
614 save32bit = SDL_TRUE;
615 }
616#endif /* SAVE_32BIT_BMP */
617
618 if (saveme->format->palette && !save32bit) {
619 if (saveme->format->BitsPerPixel == 8) {
620 surface = saveme;
621 } else {
622 SDL_SetError("%d bpp BMP files not supported",
623 saveme->format->BitsPerPixel);
624 }
625 } else if ((saveme->format->BitsPerPixel == 24) && !save32bit &&
626#if SDL_BYTEORDER == SDL_LIL_ENDIAN
627 (saveme->format->Rmask == 0x00FF0000) &&
628 (saveme->format->Gmask == 0x0000FF00) &&
629 (saveme->format->Bmask == 0x000000FF)
630#else
631 (saveme->format->Rmask == 0x000000FF) &&
632 (saveme->format->Gmask == 0x0000FF00) &&
633 (saveme->format->Bmask == 0x00FF0000)
634#endif
635 ) {
636 surface = saveme;
637 } else {
638 SDL_PixelFormat format;
639
640 /* If the surface has a colorkey or alpha channel we'll save a
641 32-bit BMP with alpha channel, otherwise save a 24-bit BMP. */
642 if (save32bit) {
643 SDL_InitFormat(&format, SDL_PIXELFORMAT_BGRA32);
644 } else {
645 SDL_InitFormat(&format, SDL_PIXELFORMAT_BGR24);
646 }
647 surface = SDL_ConvertSurface(saveme, &format, 0);
648 if (!surface) {
649 SDL_SetError("Couldn't convert image to %d bpp",
650 format.BitsPerPixel);
651 }
652 }
653 } else {
654 /* Set no error here because it may overwrite a more useful message from
655 SDL_RWFromFile() if SDL_SaveBMP_RW() is called from SDL_SaveBMP(). */
656 return -1;
657 }
658
659 if (save32bit) {
660 saveLegacyBMP = SDL_GetHintBoolean(SDL_HINT_BMP_SAVE_LEGACY_FORMAT, SDL_FALSE);
661 }
662
663 if (surface && (SDL_LockSurface(surface) == 0)) {
664 const int bw = surface->w * surface->format->BytesPerPixel;
665
666 /* Set the BMP file header values */
667 bfSize = 0; /* We'll write this when we're done */
668 bfReserved1 = 0;
669 bfReserved2 = 0;
670 bfOffBits = 0; /* We'll write this when we're done */
671
672 /* Write the BMP file header values */
673 fp_offset = SDL_RWtell(dst);
674 SDL_ClearError();
675 SDL_RWwrite(dst, magic, 2, 1);
676 SDL_WriteLE32(dst, bfSize);
677 SDL_WriteLE16(dst, bfReserved1);
678 SDL_WriteLE16(dst, bfReserved2);
679 SDL_WriteLE32(dst, bfOffBits);
680
681 /* Set the BMP info values */
682 biSize = 40;
683 biWidth = surface->w;
684 biHeight = surface->h;
685 biPlanes = 1;
686 biBitCount = surface->format->BitsPerPixel;
687 biCompression = BI_RGB;
688 biSizeImage = surface->h * surface->pitch;
689 biXPelsPerMeter = 0;
690 biYPelsPerMeter = 0;
691 if (surface->format->palette) {
692 biClrUsed = surface->format->palette->ncolors;
693 } else {
694 biClrUsed = 0;
695 }
696 biClrImportant = 0;
697
698 /* Set the BMP info values for the version 4 header */
699 if (save32bit && !saveLegacyBMP) {
700 biSize = 108;
701 biCompression = BI_BITFIELDS;
702 /* The BMP format is always little endian, these masks stay the same */
703 bV4RedMask = 0x00ff0000;
704 bV4GreenMask = 0x0000ff00;
705 bV4BlueMask = 0x000000ff;
706 bV4AlphaMask = 0xff000000;
707 bV4CSType = LCS_WINDOWS_COLOR_SPACE;
708 bV4GammaRed = 0;
709 bV4GammaGreen = 0;
710 bV4GammaBlue = 0;
711 }
712
713 /* Write the BMP info values */
714 SDL_WriteLE32(dst, biSize);
715 SDL_WriteLE32(dst, biWidth);
716 SDL_WriteLE32(dst, biHeight);
717 SDL_WriteLE16(dst, biPlanes);
718 SDL_WriteLE16(dst, biBitCount);
719 SDL_WriteLE32(dst, biCompression);
720 SDL_WriteLE32(dst, biSizeImage);
721 SDL_WriteLE32(dst, biXPelsPerMeter);
722 SDL_WriteLE32(dst, biYPelsPerMeter);
723 SDL_WriteLE32(dst, biClrUsed);
724 SDL_WriteLE32(dst, biClrImportant);
725
726 /* Write the BMP info values for the version 4 header */
727 if (save32bit && !saveLegacyBMP) {
728 SDL_WriteLE32(dst, bV4RedMask);
729 SDL_WriteLE32(dst, bV4GreenMask);
730 SDL_WriteLE32(dst, bV4BlueMask);
731 SDL_WriteLE32(dst, bV4AlphaMask);
732 SDL_WriteLE32(dst, bV4CSType);
733 for (i = 0; i < 3 * 3; i++) {
734 SDL_WriteLE32(dst, bV4Endpoints[i]);
735 }
736 SDL_WriteLE32(dst, bV4GammaRed);
737 SDL_WriteLE32(dst, bV4GammaGreen);
738 SDL_WriteLE32(dst, bV4GammaBlue);
739 }
740
741 /* Write the palette (in BGR color order) */
742 if (surface->format->palette) {
743 SDL_Color *colors;
744 int ncolors;
745
746 colors = surface->format->palette->colors;
747 ncolors = surface->format->palette->ncolors;
748 for (i = 0; i < ncolors; ++i) {
749 SDL_RWwrite(dst, &colors[i].b, 1, 1);
750 SDL_RWwrite(dst, &colors[i].g, 1, 1);
751 SDL_RWwrite(dst, &colors[i].r, 1, 1);
752 SDL_RWwrite(dst, &colors[i].a, 1, 1);
753 }
754 }
755
756 /* Write the bitmap offset */
757 bfOffBits = (Uint32)(SDL_RWtell(dst) - fp_offset);
758 if (SDL_RWseek(dst, fp_offset + 10, RW_SEEK_SET) < 0) {
759 SDL_Error(SDL_EFSEEK);
760 }
761 SDL_WriteLE32(dst, bfOffBits);
762 if (SDL_RWseek(dst, fp_offset + bfOffBits, RW_SEEK_SET) < 0) {
763 SDL_Error(SDL_EFSEEK);
764 }
765
766 /* Write the bitmap image upside down */
767 bits = (Uint8 *) surface->pixels + (surface->h * surface->pitch);
768 pad = ((bw % 4) ? (4 - (bw % 4)) : 0);
769 while (bits > (Uint8 *) surface->pixels) {
770 bits -= surface->pitch;
771 if (SDL_RWwrite(dst, bits, 1, bw) != bw) {
772 SDL_Error(SDL_EFWRITE);
773 break;
774 }
775 if (pad) {
776 const Uint8 padbyte = 0;
777 for (i = 0; i < pad; ++i) {
778 SDL_RWwrite(dst, &padbyte, 1, 1);
779 }
780 }
781 }
782
783 /* Write the BMP file size */
784 bfSize = (Uint32)(SDL_RWtell(dst) - fp_offset);
785 if (SDL_RWseek(dst, fp_offset + 2, RW_SEEK_SET) < 0) {
786 SDL_Error(SDL_EFSEEK);
787 }
788 SDL_WriteLE32(dst, bfSize);
789 if (SDL_RWseek(dst, fp_offset + bfSize, RW_SEEK_SET) < 0) {
790 SDL_Error(SDL_EFSEEK);
791 }
792
793 /* Close it up.. */
794 SDL_UnlockSurface(surface);
795 if (surface != saveme) {
796 SDL_FreeSurface(surface);
797 }
798 }
799
800 if (freedst && dst) {
801 SDL_RWclose(dst);
802 }
803 return ((SDL_strcmp(SDL_GetError(), "") == 0) ? 0 : -1);
804}
805
806/* vi: set ts=4 sw=4 expandtab: */
807