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