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