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