diff --git a/include/SDL_hints.h b/include/SDL_hints.h index f27a58260..fdb4d2bd3 100644 --- a/include/SDL_hints.h +++ b/include/SDL_hints.h @@ -669,6 +669,25 @@ extern "C" { */ #define SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4 "SDL_WINDOWS_NO_CLOSE_ON_ALT_F4" +/** + * \brief Prevent SDL from using version 4 of the bitmap header when saving BMPs. + * + * The bitmap header version 4 is required for proper alpha channel support and + * SDL will use it when required. Should this not be desired, this hint can + * force the use of the 40 byte header version which is supported everywhere. + * + * The variable can be set to the following values: + * "0" - Surfaces with a colorkey or an alpha channel are saved to a + * 32-bit BMP file with an alpha mask. SDL will use the bitmap + * header version 4 and set the alpha mask accordingly. + * "1" - Surfaces with a colorkey or an alpha channel are saved to a + * 32-bit BMP file without an alpha mask. The alpha channel data + * will be in the file, but applications are going to ignore it. + * + * The default value is "0". + */ +#define SDL_HINT_BMP_SAVE_LEGACY_FORMAT "SDL_BMP_SAVE_LEGACY_FORMAT" + /** * \brief An enumeration of hint priorities */ diff --git a/include/SDL_surface.h b/include/SDL_surface.h index e63ca8903..0fc65da98 100644 --- a/include/SDL_surface.h +++ b/include/SDL_surface.h @@ -184,6 +184,12 @@ extern DECLSPEC SDL_Surface *SDLCALL SDL_LoadBMP_RW(SDL_RWops * src, /** * Save a surface to a seekable SDL data stream (memory or file). * + * Surfaces with a 24-bit, 32-bit and paletted 8-bit format get saved in the + * BMP directly. Other RGB formats with 8-bit or higher get converted to a + * 24-bit surface or, if they have an alpha mask or a colorkey, to a 32-bit + * surface before they are saved. YUV and paletted 1-bit and 4-bit formats are + * not supported. + * * If \c freedst is non-zero, the stream will be closed after being written. * * \return 0 if successful or -1 if there was an error. diff --git a/src/video/SDL_bmp.c b/src/video/SDL_bmp.c index f80f93696..e71c6ccdb 100644 --- a/src/video/SDL_bmp.c +++ b/src/video/SDL_bmp.c @@ -32,6 +32,7 @@ This code currently supports Win32 DIBs in uncompressed 8 and 24 bpp. */ +#include "SDL_hints.h" #include "SDL_video.h" #include "SDL_assert.h" #include "SDL_endian.h" @@ -47,6 +48,11 @@ #define BI_BITFIELDS 3 #endif +/* Logical color space values for BMP files */ +#ifndef LCS_WINDOWS_COLOR_SPACE +/* 0x57696E20 == "Win " */ +#define LCS_WINDOWS_COLOR_SPACE 0x57696E20 +#endif static void CorrectAlphaChannel(SDL_Surface *surface) { @@ -457,6 +463,8 @@ SDL_SaveBMP_RW(SDL_Surface * saveme, SDL_RWops * dst, int freedst) int i, pad; SDL_Surface *surface; Uint8 *bits; + SDL_bool save32bit = SDL_FALSE; + SDL_bool saveLegacyBMP = SDL_FALSE; /* The Win32 BMP file header (14 bytes) */ char magic[2] = { 'B', 'M' }; @@ -478,14 +486,24 @@ SDL_SaveBMP_RW(SDL_Surface * saveme, SDL_RWops * dst, int freedst) Uint32 biClrUsed; Uint32 biClrImportant; + /* The additional header members from the Win32 BITMAPV4HEADER struct (108 bytes in total) */ + Uint32 bV4RedMask = 0; + Uint32 bV4GreenMask = 0; + Uint32 bV4BlueMask = 0; + Uint32 bV4AlphaMask = 0; + Uint32 bV4CSType = 0; + Sint32 bV4Endpoints[3 * 3] = {0}; + Uint32 bV4GammaRed = 0; + Uint32 bV4GammaGreen = 0; + Uint32 bV4GammaBlue = 0; + /* Make sure we have somewhere to save */ surface = NULL; if (dst) { - SDL_bool save32bit = SDL_FALSE; #ifdef SAVE_32BIT_BMP /* We can save alpha information in a 32-bit BMP */ - if (saveme->map->info.flags & SDL_COPY_COLORKEY || - saveme->format->Amask) { + if (saveme->format->BitsPerPixel >= 8 && (saveme->format->Amask || + saveme->map->info.flags & SDL_COPY_COLORKEY)) { save32bit = SDL_TRUE; } #endif /* SAVE_32BIT_BMP */ @@ -497,7 +515,7 @@ SDL_SaveBMP_RW(SDL_Surface * saveme, SDL_RWops * dst, int freedst) SDL_SetError("%d bpp BMP files not supported", saveme->format->BitsPerPixel); } - } else if ((saveme->format->BitsPerPixel == 24) && + } else if ((saveme->format->BitsPerPixel == 24) && !save32bit && #if SDL_BYTEORDER == SDL_LIL_ENDIAN (saveme->format->Rmask == 0x00FF0000) && (saveme->format->Gmask == 0x0000FF00) && @@ -537,6 +555,13 @@ SDL_SaveBMP_RW(SDL_Surface * saveme, SDL_RWops * dst, int freedst) return -1; } + if (save32bit) { + const char *hint = SDL_GetHint(SDL_HINT_BMP_SAVE_LEGACY_FORMAT); + if (hint != NULL && (hint[0] == '1' && hint[1] == 0)) { + saveLegacyBMP = SDL_TRUE; + } + } + if (surface && (SDL_LockSurface(surface) == 0)) { const int bw = surface->w * surface->format->BytesPerPixel; @@ -572,6 +597,21 @@ SDL_SaveBMP_RW(SDL_Surface * saveme, SDL_RWops * dst, int freedst) } biClrImportant = 0; + /* Set the BMP info values for the version 4 header */ + if (save32bit && !saveLegacyBMP) { + biSize = 108; + biCompression = BI_BITFIELDS; + /* The BMP format is always little endian, these masks stay the same */ + bV4RedMask = 0x00ff0000; + bV4GreenMask = 0x0000ff00; + bV4BlueMask = 0x000000ff; + bV4AlphaMask = 0xff000000; + bV4CSType = LCS_WINDOWS_COLOR_SPACE; + bV4GammaRed = 0; + bV4GammaGreen = 0; + bV4GammaBlue = 0; + } + /* Write the BMP info values */ SDL_WriteLE32(dst, biSize); SDL_WriteLE32(dst, biWidth); @@ -585,6 +625,21 @@ SDL_SaveBMP_RW(SDL_Surface * saveme, SDL_RWops * dst, int freedst) SDL_WriteLE32(dst, biClrUsed); SDL_WriteLE32(dst, biClrImportant); + /* Write the BMP info values for the version 4 header */ + if (save32bit && !saveLegacyBMP) { + SDL_WriteLE32(dst, bV4RedMask); + SDL_WriteLE32(dst, bV4GreenMask); + SDL_WriteLE32(dst, bV4BlueMask); + SDL_WriteLE32(dst, bV4AlphaMask); + SDL_WriteLE32(dst, bV4CSType); + for (i = 0; i < 3 * 3; i++) { + SDL_WriteLE32(dst, bV4Endpoints[i]); + } + SDL_WriteLE32(dst, bV4GammaRed); + SDL_WriteLE32(dst, bV4GammaGreen); + SDL_WriteLE32(dst, bV4GammaBlue); + } + /* Write the palette (in BGR color order) */ if (surface->format->palette) { SDL_Color *colors;