2 Simple DirectMedia Layer
3 Copyright (C) 1997-2016 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_video.h"
36 #include "SDL_assert.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
51 static void CorrectAlphaChannel(SDL_Surface *surface)
53 /* Check to see if there is any alpha channel data */
54 SDL_bool hasAlpha = SDL_FALSE;
55 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
56 int alphaChannelOffset = 0;
58 int alphaChannelOffset = 3;
60 Uint8 *alpha = ((Uint8*)surface->pixels) + alphaChannelOffset;
61 Uint8 *end = alpha + surface->h * surface->pitch;
72 alpha = ((Uint8*)surface->pixels) + alphaChannelOffset;
74 *alpha = SDL_ALPHA_OPAQUE;
81 SDL_LoadBMP_RW(SDL_RWops * src, int freesrc)
97 SDL_bool haveRGBMasks = SDL_FALSE;
98 SDL_bool haveAlphaMask = SDL_FALSE;
99 SDL_bool correctAlpha = SDL_FALSE;
101 /* The Win32 BMP file header (14 bytes) */
103 /* Uint32 bfSize = 0; */
104 /* Uint16 bfReserved1 = 0; */
105 /* Uint16 bfReserved2 = 0; */
106 Uint32 bfOffBits = 0;
108 /* The Win32 BITMAPINFOHEADER struct (40 bytes) */
112 /* Uint16 biPlanes = 0; */
113 Uint16 biBitCount = 0;
114 Uint32 biCompression = 0;
115 /* Uint32 biSizeImage = 0; */
116 /* Sint32 biXPelsPerMeter = 0; */
117 /* Sint32 biYPelsPerMeter = 0; */
118 Uint32 biClrUsed = 0;
119 /* Uint32 biClrImportant = 0; */
121 /* Make sure we are passed a valid data source */
123 was_error = SDL_FALSE;
125 was_error = SDL_TRUE;
129 /* Read in the BMP file header */
130 fp_offset = SDL_RWtell(src);
132 if (SDL_RWread(src, magic, 1, 2) != 2) {
133 SDL_Error(SDL_EFREAD);
134 was_error = SDL_TRUE;
137 if (SDL_strncmp(magic, "BM", 2) != 0) {
138 SDL_SetError("File is not a Windows BMP file");
139 was_error = SDL_TRUE;
142 /* bfSize = */ SDL_ReadLE32(src);
143 /* bfReserved1 = */ SDL_ReadLE16(src);
144 /* bfReserved2 = */ SDL_ReadLE16(src);
145 bfOffBits = SDL_ReadLE32(src);
147 /* Read the Win32 BITMAPINFOHEADER */
148 biSize = SDL_ReadLE32(src);
149 if (biSize == 12) { /* really old BITMAPCOREHEADER */
150 biWidth = (Uint32) SDL_ReadLE16(src);
151 biHeight = (Uint32) SDL_ReadLE16(src);
152 /* biPlanes = */ SDL_ReadLE16(src);
153 biBitCount = SDL_ReadLE16(src);
154 biCompression = BI_RGB;
155 } else if (biSize >= 40) { /* some version of BITMAPINFOHEADER */
157 biWidth = SDL_ReadLE32(src);
158 biHeight = SDL_ReadLE32(src);
159 /* biPlanes = */ SDL_ReadLE16(src);
160 biBitCount = SDL_ReadLE16(src);
161 biCompression = SDL_ReadLE32(src);
162 /* biSizeImage = */ SDL_ReadLE32(src);
163 /* biXPelsPerMeter = */ SDL_ReadLE32(src);
164 /* biYPelsPerMeter = */ SDL_ReadLE32(src);
165 biClrUsed = SDL_ReadLE32(src);
166 /* biClrImportant = */ SDL_ReadLE32(src);
168 /* 64 == BITMAPCOREHEADER2, an incompatible OS/2 2.x extension. Skip this stuff for now. */
170 /* ignore these extra fields. */
171 if (biCompression == BI_BITFIELDS) {
172 /* this value is actually huffman compression in this variant. */
173 SDL_SetError("Compressed BMP files not supported");
174 was_error = SDL_TRUE;
178 /* This is complicated. If compression is BI_BITFIELDS, then
179 we have 3 DWORDS that specify the RGB masks. This is either
180 stored here in an BITMAPV2INFOHEADER (which only differs in
181 that it adds these RGB masks) and biSize >= 52, or we've got
182 these masks stored in the exact same place, but strictly
183 speaking, this is the bmiColors field in BITMAPINFO immediately
184 following the legacy v1 info header, just past biSize. */
185 if (biCompression == BI_BITFIELDS) {
186 haveRGBMasks = SDL_TRUE;
187 Rmask = SDL_ReadLE32(src);
188 Gmask = SDL_ReadLE32(src);
189 Bmask = SDL_ReadLE32(src);
191 /* ...v3 adds an alpha mask. */
192 if (biSize >= 56) { /* BITMAPV3INFOHEADER; adds alpha mask */
193 haveAlphaMask = SDL_TRUE;
194 Amask = SDL_ReadLE32(src);
197 /* the mask fields are ignored for v2+ headers if not BI_BITFIELD. */
198 if (biSize >= 52) { /* BITMAPV2INFOHEADER; adds RGB masks */
199 /*Rmask = */ SDL_ReadLE32(src);
200 /*Gmask = */ SDL_ReadLE32(src);
201 /*Bmask = */ SDL_ReadLE32(src);
203 if (biSize >= 56) { /* BITMAPV3INFOHEADER; adds alpha mask */
204 /*Amask = */ SDL_ReadLE32(src);
208 /* Insert other fields here; Wikipedia and MSDN say we're up to
209 v5 of this header, but we ignore those for now (they add gamma,
210 color spaces, etc). Ignoring the weird OS/2 2.x format, we
211 currently parse up to v3 correctly (hopefully!). */
214 /* skip any header bytes we didn't handle... */
215 headerSize = (Uint32) (SDL_RWtell(src) - (fp_offset + 14));
216 if (biSize > headerSize) {
217 SDL_RWseek(src, (biSize - headerSize), RW_SEEK_CUR);
222 biHeight = -biHeight;
227 /* Check for read error */
228 if (SDL_strcmp(SDL_GetError(), "") != 0) {
229 was_error = SDL_TRUE;
233 /* Expand 1 and 4 bit bitmaps to 8 bits per pixel */
234 switch (biBitCount) {
237 ExpandBMP = biBitCount;
245 /* We don't support any BMP compression right now */
246 switch (biCompression) {
248 /* If there are no masks, use the defaults */
249 SDL_assert(!haveRGBMasks);
250 SDL_assert(!haveAlphaMask);
251 /* Default values for the BMP format */
252 switch (biBitCount) {
260 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
271 /* We don't know if this has alpha channel or not */
272 correctAlpha = SDL_TRUE;
284 break; /* we handled this in the info header. */
287 SDL_SetError("Compressed BMP files not supported");
288 was_error = SDL_TRUE;
292 /* Create a compatible surface, note that the colors are RGB ordered */
294 SDL_CreateRGBSurface(0, biWidth, biHeight, biBitCount, Rmask, Gmask,
296 if (surface == NULL) {
297 was_error = SDL_TRUE;
301 /* Load the palette, if any */
302 palette = (surface->format)->palette;
304 SDL_assert(biBitCount <= 8);
305 if (biClrUsed == 0) {
306 biClrUsed = 1 << biBitCount;
308 if ((int) biClrUsed > palette->ncolors) {
310 int ncolors = biClrUsed;
312 (SDL_Color *) SDL_realloc(palette->colors,
314 sizeof(*palette->colors));
317 was_error = SDL_TRUE;
320 palette->ncolors = ncolors;
321 palette->colors = colors;
322 } else if ((int) biClrUsed < palette->ncolors) {
323 palette->ncolors = biClrUsed;
326 for (i = 0; i < (int) biClrUsed; ++i) {
327 SDL_RWread(src, &palette->colors[i].b, 1, 1);
328 SDL_RWread(src, &palette->colors[i].g, 1, 1);
329 SDL_RWread(src, &palette->colors[i].r, 1, 1);
330 palette->colors[i].a = SDL_ALPHA_OPAQUE;
333 for (i = 0; i < (int) biClrUsed; ++i) {
334 SDL_RWread(src, &palette->colors[i].b, 1, 1);
335 SDL_RWread(src, &palette->colors[i].g, 1, 1);
336 SDL_RWread(src, &palette->colors[i].r, 1, 1);
337 SDL_RWread(src, &palette->colors[i].a, 1, 1);
339 /* According to Microsoft documentation, the fourth element
340 is reserved and must be zero, so we shouldn't treat it as
343 palette->colors[i].a = SDL_ALPHA_OPAQUE;
348 /* Read the surface pixels. Note that the bmp image is upside down */
349 if (SDL_RWseek(src, fp_offset + bfOffBits, RW_SEEK_SET) < 0) {
350 SDL_Error(SDL_EFSEEK);
351 was_error = SDL_TRUE;
354 top = (Uint8 *)surface->pixels;
355 end = (Uint8 *)surface->pixels+(surface->h*surface->pitch);
358 bmpPitch = (biWidth + 7) >> 3;
359 pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0);
362 bmpPitch = (biWidth + 1) >> 1;
363 pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0);
366 pad = ((surface->pitch % 4) ? (4 - (surface->pitch % 4)) : 0);
372 bits = end - surface->pitch;
374 while (bits >= top && bits < end) {
379 int shift = (8 - ExpandBMP);
380 for (i = 0; i < surface->w; ++i) {
381 if (i % (8 / ExpandBMP) == 0) {
382 if (!SDL_RWread(src, &pixel, 1, 1)) {
383 SDL_SetError("Error reading from BMP");
384 was_error = SDL_TRUE;
388 *(bits + i) = (pixel >> shift);
395 if (SDL_RWread(src, bits, 1, surface->pitch)
397 SDL_Error(SDL_EFREAD);
398 was_error = SDL_TRUE;
401 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
402 /* Byte-swap the pixels if needed. Note that the 24bpp
403 case has already been taken care of above. */
404 switch (biBitCount) {
407 Uint16 *pix = (Uint16 *) bits;
408 for (i = 0; i < surface->w; i++)
409 pix[i] = SDL_Swap16(pix[i]);
414 Uint32 *pix = (Uint32 *) bits;
415 for (i = 0; i < surface->w; i++)
416 pix[i] = SDL_Swap32(pix[i]);
423 /* Skip padding bytes, ugh */
426 for (i = 0; i < pad; ++i) {
427 SDL_RWread(src, &padbyte, 1, 1);
431 bits += surface->pitch;
433 bits -= surface->pitch;
437 CorrectAlphaChannel(surface);
442 SDL_RWseek(src, fp_offset, RW_SEEK_SET);
444 SDL_FreeSurface(surface);
447 if (freesrc && src) {
454 SDL_SaveBMP_RW(SDL_Surface * saveme, SDL_RWops * dst, int freedst)
458 SDL_Surface *surface;
461 /* The Win32 BMP file header (14 bytes) */
462 char magic[2] = { 'B', 'M' };
468 /* The Win32 BITMAPINFOHEADER struct (40 bytes) */
474 Uint32 biCompression;
476 Sint32 biXPelsPerMeter;
477 Sint32 biYPelsPerMeter;
479 Uint32 biClrImportant;
481 /* Make sure we have somewhere to save */
484 SDL_bool save32bit = SDL_FALSE;
485 #ifdef SAVE_32BIT_BMP
486 /* We can save alpha information in a 32-bit BMP */
487 if (saveme->map->info.flags & SDL_COPY_COLORKEY ||
488 saveme->format->Amask) {
489 save32bit = SDL_TRUE;
491 #endif /* SAVE_32BIT_BMP */
493 if (saveme->format->palette && !save32bit) {
494 if (saveme->format->BitsPerPixel == 8) {
497 SDL_SetError("%d bpp BMP files not supported",
498 saveme->format->BitsPerPixel);
500 } else if ((saveme->format->BitsPerPixel == 24) &&
501 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
502 (saveme->format->Rmask == 0x00FF0000) &&
503 (saveme->format->Gmask == 0x0000FF00) &&
504 (saveme->format->Bmask == 0x000000FF)
506 (saveme->format->Rmask == 0x000000FF) &&
507 (saveme->format->Gmask == 0x0000FF00) &&
508 (saveme->format->Bmask == 0x00FF0000)
513 SDL_PixelFormat format;
515 /* If the surface has a colorkey or alpha channel we'll save a
516 32-bit BMP with alpha channel, otherwise save a 24-bit BMP. */
518 SDL_InitFormat(&format,
519 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
520 SDL_PIXELFORMAT_ARGB8888
522 SDL_PIXELFORMAT_BGRA8888
526 SDL_InitFormat(&format, SDL_PIXELFORMAT_BGR24);
528 surface = SDL_ConvertSurface(saveme, &format, 0);
530 SDL_SetError("Couldn't convert image to %d bpp",
531 format.BitsPerPixel);
535 /* Set no error here because it may overwrite a more useful message from
536 SDL_RWFromFile() if SDL_SaveBMP_RW() is called from SDL_SaveBMP(). */
540 if (surface && (SDL_LockSurface(surface) == 0)) {
541 const int bw = surface->w * surface->format->BytesPerPixel;
543 /* Set the BMP file header values */
544 bfSize = 0; /* We'll write this when we're done */
547 bfOffBits = 0; /* We'll write this when we're done */
549 /* Write the BMP file header values */
550 fp_offset = SDL_RWtell(dst);
552 SDL_RWwrite(dst, magic, 2, 1);
553 SDL_WriteLE32(dst, bfSize);
554 SDL_WriteLE16(dst, bfReserved1);
555 SDL_WriteLE16(dst, bfReserved2);
556 SDL_WriteLE32(dst, bfOffBits);
558 /* Set the BMP info values */
560 biWidth = surface->w;
561 biHeight = surface->h;
563 biBitCount = surface->format->BitsPerPixel;
564 biCompression = BI_RGB;
565 biSizeImage = surface->h * surface->pitch;
568 if (surface->format->palette) {
569 biClrUsed = surface->format->palette->ncolors;
575 /* Write the BMP info values */
576 SDL_WriteLE32(dst, biSize);
577 SDL_WriteLE32(dst, biWidth);
578 SDL_WriteLE32(dst, biHeight);
579 SDL_WriteLE16(dst, biPlanes);
580 SDL_WriteLE16(dst, biBitCount);
581 SDL_WriteLE32(dst, biCompression);
582 SDL_WriteLE32(dst, biSizeImage);
583 SDL_WriteLE32(dst, biXPelsPerMeter);
584 SDL_WriteLE32(dst, biYPelsPerMeter);
585 SDL_WriteLE32(dst, biClrUsed);
586 SDL_WriteLE32(dst, biClrImportant);
588 /* Write the palette (in BGR color order) */
589 if (surface->format->palette) {
593 colors = surface->format->palette->colors;
594 ncolors = surface->format->palette->ncolors;
595 for (i = 0; i < ncolors; ++i) {
596 SDL_RWwrite(dst, &colors[i].b, 1, 1);
597 SDL_RWwrite(dst, &colors[i].g, 1, 1);
598 SDL_RWwrite(dst, &colors[i].r, 1, 1);
599 SDL_RWwrite(dst, &colors[i].a, 1, 1);
603 /* Write the bitmap offset */
604 bfOffBits = (Uint32)(SDL_RWtell(dst) - fp_offset);
605 if (SDL_RWseek(dst, fp_offset + 10, RW_SEEK_SET) < 0) {
606 SDL_Error(SDL_EFSEEK);
608 SDL_WriteLE32(dst, bfOffBits);
609 if (SDL_RWseek(dst, fp_offset + bfOffBits, RW_SEEK_SET) < 0) {
610 SDL_Error(SDL_EFSEEK);
613 /* Write the bitmap image upside down */
614 bits = (Uint8 *) surface->pixels + (surface->h * surface->pitch);
615 pad = ((bw % 4) ? (4 - (bw % 4)) : 0);
616 while (bits > (Uint8 *) surface->pixels) {
617 bits -= surface->pitch;
618 if (SDL_RWwrite(dst, bits, 1, bw) != bw) {
619 SDL_Error(SDL_EFWRITE);
623 const Uint8 padbyte = 0;
624 for (i = 0; i < pad; ++i) {
625 SDL_RWwrite(dst, &padbyte, 1, 1);
630 /* Write the BMP file size */
631 bfSize = (Uint32)(SDL_RWtell(dst) - fp_offset);
632 if (SDL_RWseek(dst, fp_offset + 2, RW_SEEK_SET) < 0) {
633 SDL_Error(SDL_EFSEEK);
635 SDL_WriteLE32(dst, bfSize);
636 if (SDL_RWseek(dst, fp_offset + bfSize, RW_SEEK_SET) < 0) {
637 SDL_Error(SDL_EFSEEK);
641 SDL_UnlockSurface(surface);
642 if (surface != saveme) {
643 SDL_FreeSurface(surface);
647 if (freedst && dst) {
650 return ((SDL_strcmp(SDL_GetError(), "") == 0) ? 0 : -1);
653 /* vi: set ts=4 sw=4 expandtab: */