SDL_bmp.c (20964B)
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2014 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_video.h" 36#include "SDL_assert.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 51static void CorrectAlphaChannel(SDL_Surface *surface) 52{ 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; 57#else 58 int alphaChannelOffset = 3; 59#endif 60 Uint8 *alpha = ((Uint8*)surface->pixels) + alphaChannelOffset; 61 Uint8 *end = alpha + surface->h * surface->pitch; 62 63 while (alpha < end) { 64 if (*alpha != 0) { 65 hasAlpha = SDL_TRUE; 66 break; 67 } 68 alpha += 4; 69 } 70 71 if (!hasAlpha) { 72 alpha = ((Uint8*)surface->pixels) + alphaChannelOffset; 73 while (alpha < end) { 74 *alpha = SDL_ALPHA_OPAQUE; 75 alpha += 4; 76 } 77 } 78} 79 80SDL_Surface * 81SDL_LoadBMP_RW(SDL_RWops * src, int freesrc) 82{ 83 SDL_bool was_error; 84 Sint64 fp_offset = 0; 85 int bmpPitch; 86 int i, pad; 87 SDL_Surface *surface; 88 Uint32 Rmask = 0; 89 Uint32 Gmask = 0; 90 Uint32 Bmask = 0; 91 Uint32 Amask = 0; 92 SDL_Palette *palette; 93 Uint8 *bits; 94 Uint8 *top, *end; 95 SDL_bool topDown; 96 int ExpandBMP; 97 SDL_bool haveRGBMasks = SDL_FALSE; 98 SDL_bool haveAlphaMask = SDL_FALSE; 99 SDL_bool correctAlpha = SDL_FALSE; 100 101 /* The Win32 BMP file header (14 bytes) */ 102 char magic[2]; 103 /* Uint32 bfSize = 0; */ 104 /* Uint16 bfReserved1 = 0; */ 105 /* Uint16 bfReserved2 = 0; */ 106 Uint32 bfOffBits = 0; 107 108 /* The Win32 BITMAPINFOHEADER struct (40 bytes) */ 109 Uint32 biSize = 0; 110 Sint32 biWidth = 0; 111 Sint32 biHeight = 0; 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; */ 120 121 /* Make sure we are passed a valid data source */ 122 surface = NULL; 123 was_error = SDL_FALSE; 124 if (src == NULL) { 125 was_error = SDL_TRUE; 126 goto done; 127 } 128 129 /* Read in the BMP file header */ 130 fp_offset = SDL_RWtell(src); 131 SDL_ClearError(); 132 if (SDL_RWread(src, magic, 1, 2) != 2) { 133 SDL_Error(SDL_EFREAD); 134 was_error = SDL_TRUE; 135 goto done; 136 } 137 if (SDL_strncmp(magic, "BM", 2) != 0) { 138 SDL_SetError("File is not a Windows BMP file"); 139 was_error = SDL_TRUE; 140 goto done; 141 } 142 /* bfSize = */ SDL_ReadLE32(src); 143 /* bfReserved1 = */ SDL_ReadLE16(src); 144 /* bfReserved2 = */ SDL_ReadLE16(src); 145 bfOffBits = SDL_ReadLE32(src); 146 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 */ 156 Uint32 headerSize; 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); 167 168 /* 64 == BITMAPCOREHEADER2, an incompatible OS/2 2.x extension. Skip this stuff for now. */ 169 if (biSize == 64) { 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; 175 goto done; 176 } 177 } else { 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); 190 191 /* ...v3 adds an alpha mask. */ 192 if (biSize >= 56) { /* BITMAPV3INFOHEADER; adds alpha mask */ 193 haveAlphaMask = SDL_TRUE; 194 Amask = SDL_ReadLE32(src); 195 } 196 } else { 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); 202 } 203 if (biSize >= 56) { /* BITMAPV3INFOHEADER; adds alpha mask */ 204 /*Amask = */ SDL_ReadLE32(src); 205 } 206 } 207 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!). */ 212 } 213 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); 218 } 219 } 220 if (biHeight < 0) { 221 topDown = SDL_TRUE; 222 biHeight = -biHeight; 223 } else { 224 topDown = SDL_FALSE; 225 } 226 227 /* Check for read error */ 228 if (SDL_strcmp(SDL_GetError(), "") != 0) { 229 was_error = SDL_TRUE; 230 goto done; 231 } 232 233 /* Expand 1 and 4 bit bitmaps to 8 bits per pixel */ 234 switch (biBitCount) { 235 case 1: 236 case 4: 237 ExpandBMP = biBitCount; 238 biBitCount = 8; 239 break; 240 default: 241 ExpandBMP = 0; 242 break; 243 } 244 245 /* We don't support any BMP compression right now */ 246 switch (biCompression) { 247 case BI_RGB: 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) { 253 case 15: 254 case 16: 255 Rmask = 0x7C00; 256 Gmask = 0x03E0; 257 Bmask = 0x001F; 258 break; 259 case 24: 260#if SDL_BYTEORDER == SDL_BIG_ENDIAN 261 Rmask = 0x000000FF; 262 Gmask = 0x0000FF00; 263 Bmask = 0x00FF0000; 264#else 265 Rmask = 0x00FF0000; 266 Gmask = 0x0000FF00; 267 Bmask = 0x000000FF; 268#endif 269 break; 270 case 32: 271 /* We don't know if this has alpha channel or not */ 272 correctAlpha = SDL_TRUE; 273 Amask = 0xFF000000; 274 Rmask = 0x00FF0000; 275 Gmask = 0x0000FF00; 276 Bmask = 0x000000FF; 277 break; 278 default: 279 break; 280 } 281 break; 282 283 case BI_BITFIELDS: 284 break; /* we handled this in the info header. */ 285 286 default: 287 SDL_SetError("Compressed BMP files not supported"); 288 was_error = SDL_TRUE; 289 goto done; 290 } 291 292 /* Create a compatible surface, note that the colors are RGB ordered */ 293 surface = 294 SDL_CreateRGBSurface(0, biWidth, biHeight, biBitCount, Rmask, Gmask, 295 Bmask, Amask); 296 if (surface == NULL) { 297 was_error = SDL_TRUE; 298 goto done; 299 } 300 301 /* Load the palette, if any */ 302 palette = (surface->format)->palette; 303 if (palette) { 304 SDL_assert(biBitCount <= 8); 305 if (biClrUsed == 0) { 306 biClrUsed = 1 << biBitCount; 307 } 308 if ((int) biClrUsed > palette->ncolors) { 309 palette->ncolors = biClrUsed; 310 palette->colors = 311 (SDL_Color *) SDL_realloc(palette->colors, 312 palette->ncolors * 313 sizeof(*palette->colors)); 314 if (!palette->colors) { 315 SDL_OutOfMemory(); 316 was_error = SDL_TRUE; 317 goto done; 318 } 319 } else if ((int) biClrUsed < palette->ncolors) { 320 palette->ncolors = biClrUsed; 321 } 322 if (biSize == 12) { 323 for (i = 0; i < (int) biClrUsed; ++i) { 324 SDL_RWread(src, &palette->colors[i].b, 1, 1); 325 SDL_RWread(src, &palette->colors[i].g, 1, 1); 326 SDL_RWread(src, &palette->colors[i].r, 1, 1); 327 palette->colors[i].a = SDL_ALPHA_OPAQUE; 328 } 329 } else { 330 for (i = 0; i < (int) biClrUsed; ++i) { 331 SDL_RWread(src, &palette->colors[i].b, 1, 1); 332 SDL_RWread(src, &palette->colors[i].g, 1, 1); 333 SDL_RWread(src, &palette->colors[i].r, 1, 1); 334 SDL_RWread(src, &palette->colors[i].a, 1, 1); 335 336 /* According to Microsoft documentation, the fourth element 337 is reserved and must be zero, so we shouldn't treat it as 338 alpha. 339 */ 340 palette->colors[i].a = SDL_ALPHA_OPAQUE; 341 } 342 } 343 } 344 345 /* Read the surface pixels. Note that the bmp image is upside down */ 346 if (SDL_RWseek(src, fp_offset + bfOffBits, RW_SEEK_SET) < 0) { 347 SDL_Error(SDL_EFSEEK); 348 was_error = SDL_TRUE; 349 goto done; 350 } 351 top = (Uint8 *)surface->pixels; 352 end = (Uint8 *)surface->pixels+(surface->h*surface->pitch); 353 switch (ExpandBMP) { 354 case 1: 355 bmpPitch = (biWidth + 7) >> 3; 356 pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0); 357 break; 358 case 4: 359 bmpPitch = (biWidth + 1) >> 1; 360 pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0); 361 break; 362 default: 363 pad = ((surface->pitch % 4) ? (4 - (surface->pitch % 4)) : 0); 364 break; 365 } 366 if (topDown) { 367 bits = top; 368 } else { 369 bits = end - surface->pitch; 370 } 371 while (bits >= top && bits < end) { 372 switch (ExpandBMP) { 373 case 1: 374 case 4:{ 375 Uint8 pixel = 0; 376 int shift = (8 - ExpandBMP); 377 for (i = 0; i < surface->w; ++i) { 378 if (i % (8 / ExpandBMP) == 0) { 379 if (!SDL_RWread(src, &pixel, 1, 1)) { 380 SDL_SetError("Error reading from BMP"); 381 was_error = SDL_TRUE; 382 goto done; 383 } 384 } 385 *(bits + i) = (pixel >> shift); 386 pixel <<= ExpandBMP; 387 } 388 } 389 break; 390 391 default: 392 if (SDL_RWread(src, bits, 1, surface->pitch) 393 != surface->pitch) { 394 SDL_Error(SDL_EFREAD); 395 was_error = SDL_TRUE; 396 goto done; 397 } 398#if SDL_BYTEORDER == SDL_BIG_ENDIAN 399 /* Byte-swap the pixels if needed. Note that the 24bpp 400 case has already been taken care of above. */ 401 switch (biBitCount) { 402 case 15: 403 case 16:{ 404 Uint16 *pix = (Uint16 *) bits; 405 for (i = 0; i < surface->w; i++) 406 pix[i] = SDL_Swap16(pix[i]); 407 break; 408 } 409 410 case 32:{ 411 Uint32 *pix = (Uint32 *) bits; 412 for (i = 0; i < surface->w; i++) 413 pix[i] = SDL_Swap32(pix[i]); 414 break; 415 } 416 } 417#endif 418 break; 419 } 420 /* Skip padding bytes, ugh */ 421 if (pad) { 422 Uint8 padbyte; 423 for (i = 0; i < pad; ++i) { 424 SDL_RWread(src, &padbyte, 1, 1); 425 } 426 } 427 if (topDown) { 428 bits += surface->pitch; 429 } else { 430 bits -= surface->pitch; 431 } 432 } 433 if (correctAlpha) { 434 CorrectAlphaChannel(surface); 435 } 436 done: 437 if (was_error) { 438 if (src) { 439 SDL_RWseek(src, fp_offset, RW_SEEK_SET); 440 } 441 SDL_FreeSurface(surface); 442 surface = NULL; 443 } 444 if (freesrc && src) { 445 SDL_RWclose(src); 446 } 447 return (surface); 448} 449 450int 451SDL_SaveBMP_RW(SDL_Surface * saveme, SDL_RWops * dst, int freedst) 452{ 453 Sint64 fp_offset; 454 int i, pad; 455 SDL_Surface *surface; 456 Uint8 *bits; 457 458 /* The Win32 BMP file header (14 bytes) */ 459 char magic[2] = { 'B', 'M' }; 460 Uint32 bfSize; 461 Uint16 bfReserved1; 462 Uint16 bfReserved2; 463 Uint32 bfOffBits; 464 465 /* The Win32 BITMAPINFOHEADER struct (40 bytes) */ 466 Uint32 biSize; 467 Sint32 biWidth; 468 Sint32 biHeight; 469 Uint16 biPlanes; 470 Uint16 biBitCount; 471 Uint32 biCompression; 472 Uint32 biSizeImage; 473 Sint32 biXPelsPerMeter; 474 Sint32 biYPelsPerMeter; 475 Uint32 biClrUsed; 476 Uint32 biClrImportant; 477 478 /* Make sure we have somewhere to save */ 479 surface = NULL; 480 if (dst) { 481 SDL_bool save32bit = SDL_FALSE; 482#ifdef SAVE_32BIT_BMP 483 /* We can save alpha information in a 32-bit BMP */ 484 if (saveme->map->info.flags & SDL_COPY_COLORKEY || 485 saveme->format->Amask) { 486 save32bit = SDL_TRUE; 487 } 488#endif /* SAVE_32BIT_BMP */ 489 490 if (saveme->format->palette && !save32bit) { 491 if (saveme->format->BitsPerPixel == 8) { 492 surface = saveme; 493 } else { 494 SDL_SetError("%d bpp BMP files not supported", 495 saveme->format->BitsPerPixel); 496 } 497 } else if ((saveme->format->BitsPerPixel == 24) && 498#if SDL_BYTEORDER == SDL_LIL_ENDIAN 499 (saveme->format->Rmask == 0x00FF0000) && 500 (saveme->format->Gmask == 0x0000FF00) && 501 (saveme->format->Bmask == 0x000000FF) 502#else 503 (saveme->format->Rmask == 0x000000FF) && 504 (saveme->format->Gmask == 0x0000FF00) && 505 (saveme->format->Bmask == 0x00FF0000) 506#endif 507 ) { 508 surface = saveme; 509 } else { 510 SDL_PixelFormat format; 511 512 /* If the surface has a colorkey or alpha channel we'll save a 513 32-bit BMP with alpha channel, otherwise save a 24-bit BMP. */ 514 if (save32bit) { 515 SDL_InitFormat(&format, 516#if SDL_BYTEORDER == SDL_LIL_ENDIAN 517 SDL_PIXELFORMAT_ARGB8888 518#else 519 SDL_PIXELFORMAT_BGRA8888 520#endif 521 ); 522 } else { 523 SDL_InitFormat(&format, SDL_PIXELFORMAT_BGR24); 524 } 525 surface = SDL_ConvertSurface(saveme, &format, 0); 526 if (!surface) { 527 SDL_SetError("Couldn't convert image to %d bpp", 528 format.BitsPerPixel); 529 } 530 } 531 } 532 533 if (surface && (SDL_LockSurface(surface) == 0)) { 534 const int bw = surface->w * surface->format->BytesPerPixel; 535 536 /* Set the BMP file header values */ 537 bfSize = 0; /* We'll write this when we're done */ 538 bfReserved1 = 0; 539 bfReserved2 = 0; 540 bfOffBits = 0; /* We'll write this when we're done */ 541 542 /* Write the BMP file header values */ 543 fp_offset = SDL_RWtell(dst); 544 SDL_ClearError(); 545 SDL_RWwrite(dst, magic, 2, 1); 546 SDL_WriteLE32(dst, bfSize); 547 SDL_WriteLE16(dst, bfReserved1); 548 SDL_WriteLE16(dst, bfReserved2); 549 SDL_WriteLE32(dst, bfOffBits); 550 551 /* Set the BMP info values */ 552 biSize = 40; 553 biWidth = surface->w; 554 biHeight = surface->h; 555 biPlanes = 1; 556 biBitCount = surface->format->BitsPerPixel; 557 biCompression = BI_RGB; 558 biSizeImage = surface->h * surface->pitch; 559 biXPelsPerMeter = 0; 560 biYPelsPerMeter = 0; 561 if (surface->format->palette) { 562 biClrUsed = surface->format->palette->ncolors; 563 } else { 564 biClrUsed = 0; 565 } 566 biClrImportant = 0; 567 568 /* Write the BMP info values */ 569 SDL_WriteLE32(dst, biSize); 570 SDL_WriteLE32(dst, biWidth); 571 SDL_WriteLE32(dst, biHeight); 572 SDL_WriteLE16(dst, biPlanes); 573 SDL_WriteLE16(dst, biBitCount); 574 SDL_WriteLE32(dst, biCompression); 575 SDL_WriteLE32(dst, biSizeImage); 576 SDL_WriteLE32(dst, biXPelsPerMeter); 577 SDL_WriteLE32(dst, biYPelsPerMeter); 578 SDL_WriteLE32(dst, biClrUsed); 579 SDL_WriteLE32(dst, biClrImportant); 580 581 /* Write the palette (in BGR color order) */ 582 if (surface->format->palette) { 583 SDL_Color *colors; 584 int ncolors; 585 586 colors = surface->format->palette->colors; 587 ncolors = surface->format->palette->ncolors; 588 for (i = 0; i < ncolors; ++i) { 589 SDL_RWwrite(dst, &colors[i].b, 1, 1); 590 SDL_RWwrite(dst, &colors[i].g, 1, 1); 591 SDL_RWwrite(dst, &colors[i].r, 1, 1); 592 SDL_RWwrite(dst, &colors[i].a, 1, 1); 593 } 594 } 595 596 /* Write the bitmap offset */ 597 bfOffBits = (Uint32)(SDL_RWtell(dst) - fp_offset); 598 if (SDL_RWseek(dst, fp_offset + 10, RW_SEEK_SET) < 0) { 599 SDL_Error(SDL_EFSEEK); 600 } 601 SDL_WriteLE32(dst, bfOffBits); 602 if (SDL_RWseek(dst, fp_offset + bfOffBits, RW_SEEK_SET) < 0) { 603 SDL_Error(SDL_EFSEEK); 604 } 605 606 /* Write the bitmap image upside down */ 607 bits = (Uint8 *) surface->pixels + (surface->h * surface->pitch); 608 pad = ((bw % 4) ? (4 - (bw % 4)) : 0); 609 while (bits > (Uint8 *) surface->pixels) { 610 bits -= surface->pitch; 611 if (SDL_RWwrite(dst, bits, 1, bw) != bw) { 612 SDL_Error(SDL_EFWRITE); 613 break; 614 } 615 if (pad) { 616 const Uint8 padbyte = 0; 617 for (i = 0; i < pad; ++i) { 618 SDL_RWwrite(dst, &padbyte, 1, 1); 619 } 620 } 621 } 622 623 /* Write the BMP file size */ 624 bfSize = (Uint32)(SDL_RWtell(dst) - fp_offset); 625 if (SDL_RWseek(dst, fp_offset + 2, RW_SEEK_SET) < 0) { 626 SDL_Error(SDL_EFSEEK); 627 } 628 SDL_WriteLE32(dst, bfSize); 629 if (SDL_RWseek(dst, fp_offset + bfSize, RW_SEEK_SET) < 0) { 630 SDL_Error(SDL_EFSEEK); 631 } 632 633 /* Close it up.. */ 634 SDL_UnlockSurface(surface); 635 if (surface != saveme) { 636 SDL_FreeSurface(surface); 637 } 638 } 639 640 if (freedst && dst) { 641 SDL_RWclose(dst); 642 } 643 return ((SDL_strcmp(SDL_GetError(), "") == 0) ? 0 : -1); 644} 645 646/* vi: set ts=4 sw=4 expandtab: */