2 Simple DirectMedia Layer
3 Copyright (C) 1997-2013 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_config.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_endian.h"
37 #include "SDL_pixels_c.h"
39 #define SAVE_32BIT_BMP
41 /* Compression encodings for BMP files */
46 #define BI_BITFIELDS 3
50 static void CorrectAlphaChannel(SDL_Surface *surface)
52 /* Check to see if there is any alpha channel data */
53 SDL_bool hasAlpha = SDL_FALSE;
54 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
55 int alphaChannelOffset = 0;
57 int alphaChannelOffset = 3;
59 Uint8 *alpha = ((Uint8*)surface->pixels) + alphaChannelOffset;
60 Uint8 *end = alpha + surface->h * surface->pitch;
71 alpha = ((Uint8*)surface->pixels) + alphaChannelOffset;
73 *alpha = SDL_ALPHA_OPAQUE;
80 SDL_LoadBMP_RW(SDL_RWops * src, int freesrc)
96 SDL_bool correctAlpha = SDL_FALSE;
98 /* The Win32 BMP file header (14 bytes) */
100 /* Uint32 bfSize = 0; */
101 /* Uint16 bfReserved1 = 0; */
102 /* Uint16 bfReserved2 = 0; */
103 Uint32 bfOffBits = 0;
105 /* The Win32 BITMAPINFOHEADER struct (40 bytes) */
109 /* Uint16 biPlanes = 0; */
110 Uint16 biBitCount = 0;
111 Uint32 biCompression = 0;
112 /* Uint32 biSizeImage = 0; */
113 /* Sint32 biXPelsPerMeter = 0; */
114 /* Sint32 biYPelsPerMeter = 0; */
115 Uint32 biClrUsed = 0;
116 /* Uint32 biClrImportant = 0; */
118 /* Make sure we are passed a valid data source */
120 was_error = SDL_FALSE;
122 was_error = SDL_TRUE;
126 /* Read in the BMP file header */
127 fp_offset = SDL_RWtell(src);
129 if (SDL_RWread(src, magic, 1, 2) != 2) {
130 SDL_Error(SDL_EFREAD);
131 was_error = SDL_TRUE;
134 if (SDL_strncmp(magic, "BM", 2) != 0) {
135 SDL_SetError("File is not a Windows BMP file");
136 was_error = SDL_TRUE;
139 /* bfSize = */ SDL_ReadLE32(src);
140 /* bfReserved1 = */ SDL_ReadLE16(src);
141 /* bfReserved2 = */ SDL_ReadLE16(src);
142 bfOffBits = SDL_ReadLE32(src);
144 /* Read the Win32 BITMAPINFOHEADER */
145 biSize = SDL_ReadLE32(src);
147 biWidth = (Uint32) SDL_ReadLE16(src);
148 biHeight = (Uint32) SDL_ReadLE16(src);
149 /* biPlanes = */ SDL_ReadLE16(src);
150 biBitCount = SDL_ReadLE16(src);
151 biCompression = BI_RGB;
153 biWidth = SDL_ReadLE32(src);
154 biHeight = SDL_ReadLE32(src);
155 /* biPlanes = */ SDL_ReadLE16(src);
156 biBitCount = SDL_ReadLE16(src);
157 biCompression = SDL_ReadLE32(src);
158 /* biSizeImage = */ SDL_ReadLE32(src);
159 /* biXPelsPerMeter = */ SDL_ReadLE32(src);
160 /* biYPelsPerMeter = */ SDL_ReadLE32(src);
161 biClrUsed = SDL_ReadLE32(src);
162 /* biClrImportant = */ SDL_ReadLE32(src);
166 biHeight = -biHeight;
171 /* Check for read error */
172 if (SDL_strcmp(SDL_GetError(), "") != 0) {
173 was_error = SDL_TRUE;
177 /* Expand 1 and 4 bit bitmaps to 8 bits per pixel */
178 switch (biBitCount) {
181 ExpandBMP = biBitCount;
189 /* We don't support any BMP compression right now */
190 Rmask = Gmask = Bmask = Amask = 0;
191 switch (biCompression) {
193 /* If there are no masks, use the defaults */
194 if (bfOffBits == (14 + biSize)) {
195 /* Default values for the BMP format */
196 switch (biBitCount) {
204 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
215 /* We don't know if this has alpha channel or not */
216 correctAlpha = SDL_TRUE;
227 /* Fall through -- read the RGB masks */
230 switch (biBitCount) {
233 Rmask = SDL_ReadLE32(src);
234 Gmask = SDL_ReadLE32(src);
235 Bmask = SDL_ReadLE32(src);
238 Rmask = SDL_ReadLE32(src);
239 Gmask = SDL_ReadLE32(src);
240 Bmask = SDL_ReadLE32(src);
241 Amask = SDL_ReadLE32(src);
248 SDL_SetError("Compressed BMP files not supported");
249 was_error = SDL_TRUE;
253 /* Create a compatible surface, note that the colors are RGB ordered */
255 SDL_CreateRGBSurface(0, biWidth, biHeight, biBitCount, Rmask, Gmask,
257 if (surface == NULL) {
258 was_error = SDL_TRUE;
262 /* Load the palette, if any */
263 palette = (surface->format)->palette;
265 if (biClrUsed == 0) {
266 biClrUsed = 1 << biBitCount;
268 if ((int) biClrUsed > palette->ncolors) {
269 palette->ncolors = biClrUsed;
271 (SDL_Color *) SDL_realloc(palette->colors,
273 sizeof(*palette->colors));
274 if (!palette->colors) {
276 was_error = SDL_TRUE;
279 } else if ((int) biClrUsed < palette->ncolors) {
280 palette->ncolors = biClrUsed;
283 for (i = 0; i < (int) biClrUsed; ++i) {
284 SDL_RWread(src, &palette->colors[i].b, 1, 1);
285 SDL_RWread(src, &palette->colors[i].g, 1, 1);
286 SDL_RWread(src, &palette->colors[i].r, 1, 1);
287 palette->colors[i].a = SDL_ALPHA_OPAQUE;
290 for (i = 0; i < (int) biClrUsed; ++i) {
291 SDL_RWread(src, &palette->colors[i].b, 1, 1);
292 SDL_RWread(src, &palette->colors[i].g, 1, 1);
293 SDL_RWread(src, &palette->colors[i].r, 1, 1);
294 SDL_RWread(src, &palette->colors[i].a, 1, 1);
296 /* According to Microsoft documentation, the fourth element
297 is reserved and must be zero, so we shouldn't treat it as
300 palette->colors[i].a = SDL_ALPHA_OPAQUE;
305 /* Read the surface pixels. Note that the bmp image is upside down */
306 if (SDL_RWseek(src, fp_offset + bfOffBits, RW_SEEK_SET) < 0) {
307 SDL_Error(SDL_EFSEEK);
308 was_error = SDL_TRUE;
311 top = (Uint8 *)surface->pixels;
312 end = (Uint8 *)surface->pixels+(surface->h*surface->pitch);
315 bmpPitch = (biWidth + 7) >> 3;
316 pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0);
319 bmpPitch = (biWidth + 1) >> 1;
320 pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0);
323 pad = ((surface->pitch % 4) ? (4 - (surface->pitch % 4)) : 0);
329 bits = end - surface->pitch;
331 while (bits >= top && bits < end) {
336 int shift = (8 - ExpandBMP);
337 for (i = 0; i < surface->w; ++i) {
338 if (i % (8 / ExpandBMP) == 0) {
339 if (!SDL_RWread(src, &pixel, 1, 1)) {
340 SDL_SetError("Error reading from BMP");
341 was_error = SDL_TRUE;
345 *(bits + i) = (pixel >> shift);
352 if (SDL_RWread(src, bits, 1, surface->pitch)
354 SDL_Error(SDL_EFREAD);
355 was_error = SDL_TRUE;
358 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
359 /* Byte-swap the pixels if needed. Note that the 24bpp
360 case has already been taken care of above. */
361 switch (biBitCount) {
364 Uint16 *pix = (Uint16 *) bits;
365 for (i = 0; i < surface->w; i++)
366 pix[i] = SDL_Swap16(pix[i]);
371 Uint32 *pix = (Uint32 *) bits;
372 for (i = 0; i < surface->w; i++)
373 pix[i] = SDL_Swap32(pix[i]);
380 /* Skip padding bytes, ugh */
383 for (i = 0; i < pad; ++i) {
384 SDL_RWread(src, &padbyte, 1, 1);
388 bits += surface->pitch;
390 bits -= surface->pitch;
394 CorrectAlphaChannel(surface);
399 SDL_RWseek(src, fp_offset, RW_SEEK_SET);
402 SDL_FreeSurface(surface);
406 if (freesrc && src) {
413 SDL_SaveBMP_RW(SDL_Surface * saveme, SDL_RWops * dst, int freedst)
417 SDL_Surface *surface;
420 /* The Win32 BMP file header (14 bytes) */
421 char magic[2] = { 'B', 'M' };
427 /* The Win32 BITMAPINFOHEADER struct (40 bytes) */
433 Uint32 biCompression;
435 Sint32 biXPelsPerMeter;
436 Sint32 biYPelsPerMeter;
438 Uint32 biClrImportant;
440 /* Make sure we have somewhere to save */
443 SDL_bool save32bit = SDL_FALSE;
444 #ifdef SAVE_32BIT_BMP
445 /* We can save alpha information in a 32-bit BMP */
446 if (saveme->map->info.flags & SDL_COPY_COLORKEY ||
447 saveme->format->Amask) {
448 save32bit = SDL_TRUE;
450 #endif /* SAVE_32BIT_BMP */
452 if (saveme->format->palette && !save32bit) {
453 if (saveme->format->BitsPerPixel == 8) {
456 SDL_SetError("%d bpp BMP files not supported",
457 saveme->format->BitsPerPixel);
459 } else if ((saveme->format->BitsPerPixel == 24) &&
460 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
461 (saveme->format->Rmask == 0x00FF0000) &&
462 (saveme->format->Gmask == 0x0000FF00) &&
463 (saveme->format->Bmask == 0x000000FF)
465 (saveme->format->Rmask == 0x000000FF) &&
466 (saveme->format->Gmask == 0x0000FF00) &&
467 (saveme->format->Bmask == 0x00FF0000)
472 SDL_PixelFormat format;
474 /* If the surface has a colorkey or alpha channel we'll save a
475 32-bit BMP with alpha channel, otherwise save a 24-bit BMP. */
477 SDL_InitFormat(&format,
478 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
479 SDL_PIXELFORMAT_ARGB8888
481 SDL_PIXELFORMAT_BGRA8888
485 SDL_InitFormat(&format, SDL_PIXELFORMAT_BGR24);
487 surface = SDL_ConvertSurface(saveme, &format, 0);
489 SDL_SetError("Couldn't convert image to %d bpp",
490 format.BitsPerPixel);
495 if (surface && (SDL_LockSurface(surface) == 0)) {
496 const int bw = surface->w * surface->format->BytesPerPixel;
498 /* Set the BMP file header values */
499 bfSize = 0; /* We'll write this when we're done */
502 bfOffBits = 0; /* We'll write this when we're done */
504 /* Write the BMP file header values */
505 fp_offset = SDL_RWtell(dst);
507 SDL_RWwrite(dst, magic, 2, 1);
508 SDL_WriteLE32(dst, bfSize);
509 SDL_WriteLE16(dst, bfReserved1);
510 SDL_WriteLE16(dst, bfReserved2);
511 SDL_WriteLE32(dst, bfOffBits);
513 /* Set the BMP info values */
515 biWidth = surface->w;
516 biHeight = surface->h;
518 biBitCount = surface->format->BitsPerPixel;
519 biCompression = BI_RGB;
520 biSizeImage = surface->h * surface->pitch;
523 if (surface->format->palette) {
524 biClrUsed = surface->format->palette->ncolors;
530 /* Write the BMP info values */
531 SDL_WriteLE32(dst, biSize);
532 SDL_WriteLE32(dst, biWidth);
533 SDL_WriteLE32(dst, biHeight);
534 SDL_WriteLE16(dst, biPlanes);
535 SDL_WriteLE16(dst, biBitCount);
536 SDL_WriteLE32(dst, biCompression);
537 SDL_WriteLE32(dst, biSizeImage);
538 SDL_WriteLE32(dst, biXPelsPerMeter);
539 SDL_WriteLE32(dst, biYPelsPerMeter);
540 SDL_WriteLE32(dst, biClrUsed);
541 SDL_WriteLE32(dst, biClrImportant);
543 /* Write the palette (in BGR color order) */
544 if (surface->format->palette) {
548 colors = surface->format->palette->colors;
549 ncolors = surface->format->palette->ncolors;
550 for (i = 0; i < ncolors; ++i) {
551 SDL_RWwrite(dst, &colors[i].b, 1, 1);
552 SDL_RWwrite(dst, &colors[i].g, 1, 1);
553 SDL_RWwrite(dst, &colors[i].r, 1, 1);
554 SDL_RWwrite(dst, &colors[i].a, 1, 1);
558 /* Write the bitmap offset */
559 bfOffBits = (Uint32)(SDL_RWtell(dst) - fp_offset);
560 if (SDL_RWseek(dst, fp_offset + 10, RW_SEEK_SET) < 0) {
561 SDL_Error(SDL_EFSEEK);
563 SDL_WriteLE32(dst, bfOffBits);
564 if (SDL_RWseek(dst, fp_offset + bfOffBits, RW_SEEK_SET) < 0) {
565 SDL_Error(SDL_EFSEEK);
568 /* Write the bitmap image upside down */
569 bits = (Uint8 *) surface->pixels + (surface->h * surface->pitch);
570 pad = ((bw % 4) ? (4 - (bw % 4)) : 0);
571 while (bits > (Uint8 *) surface->pixels) {
572 bits -= surface->pitch;
573 if (SDL_RWwrite(dst, bits, 1, bw) != bw) {
574 SDL_Error(SDL_EFWRITE);
578 const Uint8 padbyte = 0;
579 for (i = 0; i < pad; ++i) {
580 SDL_RWwrite(dst, &padbyte, 1, 1);
585 /* Write the BMP file size */
586 bfSize = (Uint32)(SDL_RWtell(dst) - fp_offset);
587 if (SDL_RWseek(dst, fp_offset + 2, RW_SEEK_SET) < 0) {
588 SDL_Error(SDL_EFSEEK);
590 SDL_WriteLE32(dst, bfSize);
591 if (SDL_RWseek(dst, fp_offset + bfSize, RW_SEEK_SET) < 0) {
592 SDL_Error(SDL_EFSEEK);
596 SDL_UnlockSurface(surface);
597 if (surface != saveme) {
598 SDL_FreeSurface(surface);
602 if (freedst && dst) {
605 return ((SDL_strcmp(SDL_GetError(), "") == 0) ? 0 : -1);
608 /* vi: set ts=4 sw=4 expandtab: */