2 Simple DirectMedia Layer
3 Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
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.
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:
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.
21 #include "../SDL_internal.h"
24 Code to load and save surfaces in Windows BMP format.
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.
32 This code currently supports Win32 DIBs in uncompressed 8 and 24 bpp.
35 #include "SDL_hints.h"
36 #include "SDL_video.h"
37 #include "SDL_endian.h"
38 #include "SDL_pixels_c.h"
40 #define SAVE_32BIT_BMP
42 /* Compression encodings for BMP files */
47 #define BI_BITFIELDS 3
50 /* Logical color space values for BMP files */
51 #ifndef LCS_WINDOWS_COLOR_SPACE
52 /* 0x57696E20 == "Win " */
53 #define LCS_WINDOWS_COLOR_SPACE 0x57696E20
56 static int readRlePixels(SDL_Surface * surface, SDL_RWops * src, int isRle8)
59 | Sets the surface pixels from src. A bmp image is upside down.
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;
70 #define COPY_PIXEL(x) spot = &bits[ofs++]; if(spot >= start && spot < end) *spot = (x)
73 if (!SDL_RWread(src, &ch, 1, 1)) return 1;
75 | encoded mode starts with a run length, and then a byte
76 | with two colour indexes to alternate between for the run
80 if (!SDL_RWread(src, &pixel, 1, 1)) return 1;
81 if (isRle8) { /* 256-color bitmap, compressed */
85 } else { /* 16-color bitmap, compressed */
86 Uint8 pixel0 = pixel >> 4;
87 Uint8 pixel1 = pixel & 0x0F;
89 COPY_PIXEL(pixel0); /* even count, high nibble */
91 COPY_PIXEL(pixel1); /* odd count, low nibble */
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
101 if (!SDL_RWread(src, &ch, 1, 1)) return 1;
103 case 0: /* end of line */
105 bits -= pitch; /* go to previous */
107 case 1: /* end of bitmap */
108 return 0; /* success! */
110 if (!SDL_RWread(src, &ch, 1, 1)) return 1;
112 if (!SDL_RWread(src, &ch, 1, 1)) return 1;
113 bits -= (ch * pitch);
115 default: /* no compression */
120 if (!SDL_RWread(src, &pixel, 1, 1)) return 1;
124 needsPad = (((ch+1)>>1) & 1); /* (ch+1)>>1: bytes size */
127 if (!SDL_RWread(src, &pixel, 1, 1)) return 1;
128 COPY_PIXEL(pixel >> 4);
130 COPY_PIXEL(pixel & 0x0F);
134 /* pad at even boundary */
135 if (needsPad && !SDL_RWread(src, &ch, 1, 1)) return 1;
142 static void CorrectAlphaChannel(SDL_Surface *surface)
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;
149 int alphaChannelOffset = 3;
151 Uint8 *alpha = ((Uint8*)surface->pixels) + alphaChannelOffset;
152 Uint8 *end = alpha + surface->h * surface->pitch;
154 while (alpha < end) {
163 alpha = ((Uint8*)surface->pixels) + alphaChannelOffset;
164 while (alpha < end) {
165 *alpha = SDL_ALPHA_OPAQUE;
172 SDL_LoadBMP_RW(SDL_RWops * src, int freesrc)
175 Sint64 fp_offset = 0;
178 SDL_Surface *surface;
183 SDL_Palette *palette;
188 SDL_bool haveRGBMasks = SDL_FALSE;
189 SDL_bool haveAlphaMask = SDL_FALSE;
190 SDL_bool correctAlpha = SDL_FALSE;
192 /* The Win32 BMP file header (14 bytes) */
195 /* Uint16 bfReserved1; */
196 /* Uint16 bfReserved2; */
199 /* The Win32 BITMAPINFOHEADER struct (40 bytes) */
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; */
212 /* Make sure we are passed a valid data source */
214 was_error = SDL_FALSE;
216 was_error = SDL_TRUE;
220 /* Read in the BMP file header */
221 fp_offset = SDL_RWtell(src);
223 if (SDL_RWread(src, magic, 1, 2) != 2) {
224 SDL_Error(SDL_EFREAD);
225 was_error = SDL_TRUE;
228 if (SDL_strncmp(magic, "BM", 2) != 0) {
229 SDL_SetError("File is not a Windows BMP file");
230 was_error = SDL_TRUE;
233 /* bfSize = */ SDL_ReadLE32(src);
234 /* bfReserved1 = */ SDL_ReadLE16(src);
235 /* bfReserved2 = */ SDL_ReadLE16(src);
236 bfOffBits = SDL_ReadLE32(src);
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; */
250 /* biClrImportant = 0; */
251 } else if (biSize >= 40) { /* some version of BITMAPINFOHEADER */
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);
264 /* 64 == BITMAPCOREHEADER2, an incompatible OS/2 2.x extension. Skip this stuff for now. */
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);
279 /* ...v3 adds an alpha mask. */
280 if (biSize >= 56) { /* BITMAPV3INFOHEADER; adds alpha mask */
281 haveAlphaMask = SDL_TRUE;
282 Amask = SDL_ReadLE32(src);
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);
291 if (biSize >= 56) { /* BITMAPV3INFOHEADER; adds alpha mask */
292 /*Amask = */ SDL_ReadLE32(src);
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!). */
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);
308 if (biWidth <= 0 || biHeight == 0) {
309 SDL_SetError("BMP file with bad dimensions (%dx%d)", biWidth, biHeight);
310 was_error = SDL_TRUE;
315 biHeight = -biHeight;
320 /* Check for read error */
321 if (SDL_strcmp(SDL_GetError(), "") != 0) {
322 was_error = SDL_TRUE;
326 /* Expand 1 and 4 bit bitmaps to 8 bits per pixel */
327 switch (biBitCount) {
330 ExpandBMP = biBitCount;
339 SDL_SetError("%d-bpp BMP images are not supported", biBitCount);
340 was_error = SDL_TRUE;
347 /* RLE4 and RLE8 BMP compression is supported */
348 switch (biCompression) {
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) {
362 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
373 /* We don't know if this has alpha channel or not */
374 correctAlpha = SDL_TRUE;
386 break; /* we handled this in the info header. */
392 /* Create a compatible surface, note that the colors are RGB ordered */
394 SDL_CreateRGBSurface(0, biWidth, biHeight, biBitCount, Rmask, Gmask,
396 if (surface == NULL) {
397 was_error = SDL_TRUE;
401 /* Load the palette, if any */
402 palette = (surface->format)->palette;
404 if (SDL_RWseek(src, fp_offset+14+biSize, RW_SEEK_SET) < 0) {
405 SDL_Error(SDL_EFSEEK);
406 was_error = SDL_TRUE;
411 | guich: always use 1<<bpp b/c some bitmaps can bring wrong information
414 /* if (biClrUsed == 0) { */
415 biClrUsed = 1 << biBitCount;
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;
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);
431 /* According to Microsoft documentation, the fourth element
432 is reserved and must be zero, so we shouldn't treat it as
435 palette->colors[i].a = SDL_ALPHA_OPAQUE;
438 palette->ncolors = biClrUsed;
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;
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");
452 top = (Uint8 *)surface->pixels;
453 end = (Uint8 *)surface->pixels+(surface->h*surface->pitch);
456 bmpPitch = (biWidth + 7) >> 3;
457 pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0);
460 bmpPitch = (biWidth + 1) >> 1;
461 pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0);
464 pad = ((surface->pitch % 4) ? (4 - (surface->pitch % 4)) : 0);
470 bits = end - surface->pitch;
472 while (bits >= top && bits < end) {
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;
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;
498 if (SDL_RWread(src, bits, 1, surface->pitch) != surface->pitch) {
499 SDL_Error(SDL_EFREAD);
500 was_error = SDL_TRUE;
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;
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) {
518 Uint16 *pix = (Uint16 *) bits;
519 for (i = 0; i < surface->w; i++)
520 pix[i] = SDL_Swap16(pix[i]);
525 Uint32 *pix = (Uint32 *) bits;
526 for (i = 0; i < surface->w; i++)
527 pix[i] = SDL_Swap32(pix[i]);
534 /* Skip padding bytes, ugh */
537 for (i = 0; i < pad; ++i) {
538 SDL_RWread(src, &padbyte, 1, 1);
542 bits += surface->pitch;
544 bits -= surface->pitch;
548 CorrectAlphaChannel(surface);
553 SDL_RWseek(src, fp_offset, RW_SEEK_SET);
556 SDL_FreeSurface(surface);
560 if (freesrc && src) {
567 SDL_SaveBMP_RW(SDL_Surface * saveme, SDL_RWops * dst, int freedst)
571 SDL_Surface *surface;
573 SDL_bool save32bit = SDL_FALSE;
574 SDL_bool saveLegacyBMP = SDL_FALSE;
576 /* The Win32 BMP file header (14 bytes) */
577 char magic[2] = { 'B', 'M' };
583 /* The Win32 BITMAPINFOHEADER struct (40 bytes) */
589 Uint32 biCompression;
591 Sint32 biXPelsPerMeter;
592 Sint32 biYPelsPerMeter;
594 Uint32 biClrImportant;
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;
607 /* Make sure we have somewhere to save */
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;
616 #endif /* SAVE_32BIT_BMP */
618 if (saveme->format->palette && !save32bit) {
619 if (saveme->format->BitsPerPixel == 8) {
622 SDL_SetError("%d bpp BMP files not supported",
623 saveme->format->BitsPerPixel);
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)
631 (saveme->format->Rmask == 0x000000FF) &&
632 (saveme->format->Gmask == 0x0000FF00) &&
633 (saveme->format->Bmask == 0x00FF0000)
638 SDL_PixelFormat format;
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. */
643 SDL_InitFormat(&format, SDL_PIXELFORMAT_BGRA32);
645 SDL_InitFormat(&format, SDL_PIXELFORMAT_BGR24);
647 surface = SDL_ConvertSurface(saveme, &format, 0);
649 SDL_SetError("Couldn't convert image to %d bpp",
650 format.BitsPerPixel);
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(). */
660 saveLegacyBMP = SDL_GetHintBoolean(SDL_HINT_BMP_SAVE_LEGACY_FORMAT, SDL_FALSE);
663 if (surface && (SDL_LockSurface(surface) == 0)) {
664 const int bw = surface->w * surface->format->BytesPerPixel;
666 /* Set the BMP file header values */
667 bfSize = 0; /* We'll write this when we're done */
670 bfOffBits = 0; /* We'll write this when we're done */
672 /* Write the BMP file header values */
673 fp_offset = SDL_RWtell(dst);
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);
681 /* Set the BMP info values */
683 biWidth = surface->w;
684 biHeight = surface->h;
686 biBitCount = surface->format->BitsPerPixel;
687 biCompression = BI_RGB;
688 biSizeImage = surface->h * surface->pitch;
691 if (surface->format->palette) {
692 biClrUsed = surface->format->palette->ncolors;
698 /* Set the BMP info values for the version 4 header */
699 if (save32bit && !saveLegacyBMP) {
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;
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);
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]);
736 SDL_WriteLE32(dst, bV4GammaRed);
737 SDL_WriteLE32(dst, bV4GammaGreen);
738 SDL_WriteLE32(dst, bV4GammaBlue);
741 /* Write the palette (in BGR color order) */
742 if (surface->format->palette) {
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);
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);
761 SDL_WriteLE32(dst, bfOffBits);
762 if (SDL_RWseek(dst, fp_offset + bfOffBits, RW_SEEK_SET) < 0) {
763 SDL_Error(SDL_EFSEEK);
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);
776 const Uint8 padbyte = 0;
777 for (i = 0; i < pad; ++i) {
778 SDL_RWwrite(dst, &padbyte, 1, 1);
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);
788 SDL_WriteLE32(dst, bfSize);
789 if (SDL_RWseek(dst, fp_offset + bfSize, RW_SEEK_SET) < 0) {
790 SDL_Error(SDL_EFSEEK);
794 SDL_UnlockSurface(surface);
795 if (surface != saveme) {
796 SDL_FreeSurface(surface);
800 if (freedst && dst) {
803 return ((SDL_strcmp(SDL_GetError(), "") == 0) ? 0 : -1);
806 /* vi: set ts=4 sw=4 expandtab: */