src/video/SDL_bmp.c
author Sam Lantinga
Wed, 27 Jan 2021 21:30:25 -0800
changeset 14819 f57c6e0d2bf5
parent 14640 b2b3343a310d
permissions -rw-r--r--
Fixed compiler warnings
     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 
    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 
   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 *
   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 (%dx%d)", 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 
   566 int
   567 SDL_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: */