Atlas - SDL_bmp.c
Home / ext / SDL / src / video Lines: 1 | Size: 28211 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 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_pixels_c.h" 36#include "SDL_surface_c.h" 37 38#define SAVE_32BIT_BMP 39 40// Compression encodings for BMP files 41#ifndef BI_RGB 42#define BI_RGB 0 43#define BI_RLE8 1 44#define BI_RLE4 2 45#define BI_BITFIELDS 3 46#endif 47 48// Logical color space values for BMP files 49// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wmf/eb4bbd50-b3ce-4917-895c-be31f214797f 50#ifndef LCS_WINDOWS_COLOR_SPACE 51// 0x57696E20 == "Win " 52#define LCS_WINDOWS_COLOR_SPACE 0x57696E20 53#endif 54 55#ifndef LCS_sRGB 56// 0x73524742 == "sRGB" 57#define LCS_sRGB 0x73524742 58#endif 59 60// Logical/physical color relationship 61// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wmf/9fec0834-607d-427d-abd5-ab240fb0db38 62#ifndef LCS_GM_GRAPHICS 63#define LCS_GM_GRAPHICS 0x00000002 64#endif 65 66static bool readRlePixels(SDL_Surface *surface, SDL_IOStream *src, int isRle8) 67{ 68 /* 69 | Sets the surface pixels from src. A bmp image is upside down. 70 */ 71 int pitch = surface->pitch; 72 int height = surface->h; 73 Uint8 *start = (Uint8 *)surface->pixels; 74 Uint8 *end = start + (height * pitch); 75 Uint8 *bits = end - pitch, *spot; 76 int ofs = 0; 77 Uint8 ch; 78 Uint8 needsPad; 79 const int pixels_per_byte = (isRle8 ? 1 : 2); 80 81#define COPY_PIXEL(x) \ 82 spot = &bits[ofs++]; \ 83 if (spot >= start && spot < end) \ 84 *spot = (x) 85 86 for (;;) { 87 if (!SDL_ReadU8(src, &ch)) { 88 return false; 89 } 90 /* 91 | encoded mode starts with a run length, and then a byte 92 | with two colour indexes to alternate between for the run 93 */ 94 if (ch) { 95 Uint8 pixelvalue; 96 if (!SDL_ReadU8(src, &pixelvalue)) { 97 return false; 98 } 99 ch /= pixels_per_byte; 100 do { 101 COPY_PIXEL(pixelvalue); 102 } while (--ch); 103 } else { 104 /* 105 | A leading zero is an escape; it may signal the end of the bitmap, 106 | a cursor move, or some absolute data. 107 | zero tag may be absolute mode or an escape 108 */ 109 if (!SDL_ReadU8(src, &ch)) { 110 return false; 111 } 112 switch (ch) { 113 case 0: // end of line 114 ofs = 0; 115 bits -= pitch; // go to previous 116 break; 117 case 1: // end of bitmap 118 return true; // success! 119 case 2: // delta 120 if (!SDL_ReadU8(src, &ch)) { 121 return false; 122 } 123 ofs += ch / pixels_per_byte; 124 125 if (!SDL_ReadU8(src, &ch)) { 126 return false; 127 } 128 bits -= ((ch / pixels_per_byte) * pitch); 129 break; 130 default: // no compression 131 ch /= pixels_per_byte; 132 needsPad = (ch & 1); 133 do { 134 Uint8 pixelvalue; 135 if (!SDL_ReadU8(src, &pixelvalue)) { 136 return false; 137 } 138 COPY_PIXEL(pixelvalue); 139 } while (--ch); 140 141 // pad at even boundary 142 if (needsPad && !SDL_ReadU8(src, &ch)) { 143 return false; 144 } 145 break; 146 } 147 } 148 } 149} 150 151static void CorrectAlphaChannel(SDL_Surface *surface) 152{ 153 // Check to see if there is any alpha channel data 154 bool hasAlpha = false; 155#if SDL_BYTEORDER == SDL_BIG_ENDIAN 156 int alphaChannelOffset = 0; 157#else 158 int alphaChannelOffset = 3; 159#endif 160 Uint8 *alpha = ((Uint8 *)surface->pixels) + alphaChannelOffset; 161 Uint8 *end = alpha + surface->h * surface->pitch; 162 163 while (alpha < end) { 164 if (*alpha != 0) { 165 hasAlpha = true; 166 break; 167 } 168 alpha += 4; 169 } 170 171 if (!hasAlpha) { 172 alpha = ((Uint8 *)surface->pixels) + alphaChannelOffset; 173 while (alpha < end) { 174 *alpha = SDL_ALPHA_OPAQUE; 175 alpha += 4; 176 } 177 } 178} 179 180bool SDL_IsBMP(SDL_IOStream *src) 181{ 182 Sint64 start; 183 Uint8 magic[2]; 184 bool is_BMP; 185 186 is_BMP = false; 187 start = SDL_TellIO(src); 188 if (start >= 0) { 189 if (SDL_ReadIO(src, magic, sizeof(magic)) == sizeof(magic)) { 190 if (magic[0] == 'B' && magic[1] == 'M') { 191 is_BMP = true; 192 } 193 } 194 SDL_SeekIO(src, start, SDL_IO_SEEK_SET); 195 } 196 197 return is_BMP; 198} 199 200SDL_Surface *SDL_LoadBMP_IO(SDL_IOStream *src, bool closeio) 201{ 202 bool was_error = true; 203 Sint64 fp_offset = 0; 204 int i, pad; 205 SDL_Surface *surface; 206 Uint32 Rmask = 0; 207 Uint32 Gmask = 0; 208 Uint32 Bmask = 0; 209 Uint32 Amask = 0; 210 Uint8 *bits; 211 Uint8 *top, *end; 212 bool topDown; 213 bool haveRGBMasks = false; 214 bool haveAlphaMask = false; 215 bool correctAlpha = false; 216 217 // The Win32 BMP file header (14 bytes) 218 // char magic[2]; 219 // Uint32 bfSize; 220 // Uint16 bfReserved1; 221 // Uint16 bfReserved2; 222 Uint32 bfOffBits; 223 224 // The Win32 BITMAPINFOHEADER struct (40 bytes) 225 Uint32 biSize; 226 Sint32 biWidth = 0; 227 Sint32 biHeight = 0; 228 // Uint16 biPlanes; 229 Uint16 biBitCount = 0; 230 Uint32 biCompression = 0; 231 // Uint32 biSizeImage; 232 // Sint32 biXPelsPerMeter; 233 // Sint32 biYPelsPerMeter; 234 Uint32 biClrUsed = 0; 235 // Uint32 biClrImportant; 236 237 // Make sure we are passed a valid data source 238 surface = NULL; 239 CHECK_PARAM(!src) { 240 SDL_InvalidParamError("src"); 241 goto done; 242 } 243 244 // Read in the BMP file header 245 fp_offset = SDL_TellIO(src); 246 if (fp_offset < 0) { 247 goto done; 248 } 249 SDL_ClearError(); 250 if (!SDL_IsBMP(src)) { 251 SDL_SetError("File is not a Windows BMP file"); 252 goto done; 253 } 254 if (!SDL_ReadU16LE(src, NULL /* magic (already checked) */) || 255 !SDL_ReadU32LE(src, NULL /* bfSize */) || 256 !SDL_ReadU16LE(src, NULL /* bfReserved1 */) || 257 !SDL_ReadU16LE(src, NULL /* bfReserved2 */) || 258 !SDL_ReadU32LE(src, &bfOffBits)) { 259 goto done; 260 } 261 262 // Read the Win32 BITMAPINFOHEADER 263 if (!SDL_ReadU32LE(src, &biSize)) { 264 goto done; 265 } 266 if (biSize == 12) { // really old BITMAPCOREHEADER 267 Uint16 biWidth16, biHeight16; 268 if (!SDL_ReadU16LE(src, &biWidth16) || 269 !SDL_ReadU16LE(src, &biHeight16) || 270 !SDL_ReadU16LE(src, NULL /* biPlanes */) || 271 !SDL_ReadU16LE(src, &biBitCount)) { 272 goto done; 273 } 274 biWidth = biWidth16; 275 biHeight = biHeight16; 276 biCompression = BI_RGB; 277 // biSizeImage = 0; 278 // biXPelsPerMeter = 0; 279 // biYPelsPerMeter = 0; 280 biClrUsed = 0; 281 // biClrImportant = 0; 282 } else if (biSize >= 40) { // some version of BITMAPINFOHEADER 283 Uint32 headerSize; 284 if (!SDL_ReadS32LE(src, &biWidth) || 285 !SDL_ReadS32LE(src, &biHeight) || 286 !SDL_ReadU16LE(src, NULL /* biPlanes */) || 287 !SDL_ReadU16LE(src, &biBitCount) || 288 !SDL_ReadU32LE(src, &biCompression) || 289 !SDL_ReadU32LE(src, NULL /* biSizeImage */) || 290 !SDL_ReadU32LE(src, NULL /* biXPelsPerMeter */) || 291 !SDL_ReadU32LE(src, NULL /* biYPelsPerMeter */) || 292 !SDL_ReadU32LE(src, &biClrUsed) || 293 !SDL_ReadU32LE(src, NULL /* biClrImportant */)) { 294 goto done; 295 } 296 297 // 64 == BITMAPCOREHEADER2, an incompatible OS/2 2.x extension. Skip this stuff for now. 298 if (biSize != 64) { 299 /* This is complicated. If compression is BI_BITFIELDS, then 300 we have 3 DWORDS that specify the RGB masks. This is either 301 stored here in an BITMAPV2INFOHEADER (which only differs in 302 that it adds these RGB masks) and biSize >= 52, or we've got 303 these masks stored in the exact same place, but strictly 304 speaking, this is the bmiColors field in BITMAPINFO immediately 305 following the legacy v1 info header, just past biSize. */ 306 if (biCompression == BI_BITFIELDS) { 307 haveRGBMasks = true; 308 if (!SDL_ReadU32LE(src, &Rmask) || 309 !SDL_ReadU32LE(src, &Gmask) || 310 !SDL_ReadU32LE(src, &Bmask)) { 311 goto done; 312 } 313 314 // ...v3 adds an alpha mask. 315 if (biSize >= 56) { // BITMAPV3INFOHEADER; adds alpha mask 316 haveAlphaMask = true; 317 if (!SDL_ReadU32LE(src, &Amask)) { 318 goto done; 319 } 320 } 321 } else { 322 // the mask fields are ignored for v2+ headers if not BI_BITFIELD. 323 if (biSize >= 52) { // BITMAPV2INFOHEADER; adds RGB masks 324 if (!SDL_ReadU32LE(src, NULL /* Rmask */) || 325 !SDL_ReadU32LE(src, NULL /* Gmask */) || 326 !SDL_ReadU32LE(src, NULL /* Bmask */)) { 327 goto done; 328 } 329 } 330 if (biSize >= 56) { // BITMAPV3INFOHEADER; adds alpha mask 331 if (!SDL_ReadU32LE(src, NULL /* Amask */)) { 332 goto done; 333 } 334 } 335 } 336 337 /* Insert other fields here; Wikipedia and MSDN say we're up to 338 v5 of this header, but we ignore those for now (they add gamma, 339 color spaces, etc). Ignoring the weird OS/2 2.x format, we 340 currently parse up to v3 correctly (hopefully!). */ 341 } 342 343 // skip any header bytes we didn't handle... 344 headerSize = (Uint32)(SDL_TellIO(src) - (fp_offset + 14)); 345 if (biSize > headerSize) { 346 if (SDL_SeekIO(src, (biSize - headerSize), SDL_IO_SEEK_CUR) < 0) { 347 goto done; 348 } 349 } 350 } 351 if (biWidth <= 0 || biHeight == 0) { 352 SDL_SetError("BMP file with bad dimensions (%" SDL_PRIs32 "x%" SDL_PRIs32 ")", biWidth, biHeight); 353 goto done; 354 } 355 if (biHeight < 0) { 356 topDown = true; 357 biHeight = -biHeight; 358 } else { 359 topDown = false; 360 } 361 362 // Check for read error 363 if (SDL_strcmp(SDL_GetError(), "") != 0) { 364 goto done; 365 } 366 367 // Reject invalid bit depths 368 switch (biBitCount) { 369 case 0: 370 case 3: 371 case 5: 372 case 6: 373 case 7: 374 SDL_SetError("%u bpp BMP images are not supported", biBitCount); 375 goto done; 376 default: 377 break; 378 } 379 380 // RLE4 and RLE8 BMP compression is supported 381 switch (biCompression) { 382 case BI_RGB: 383 // If there are no masks, use the defaults 384 SDL_assert(!haveRGBMasks); 385 SDL_assert(!haveAlphaMask); 386 // Default values for the BMP format 387 switch (biBitCount) { 388 case 15: 389 case 16: 390 // SDL_PIXELFORMAT_XRGB1555 or SDL_PIXELFORMAT_ARGB1555 if Amask 391 Rmask = 0x7C00; 392 Gmask = 0x03E0; 393 Bmask = 0x001F; 394 break; 395 case 24: 396#if SDL_BYTEORDER == SDL_BIG_ENDIAN 397 // SDL_PIXELFORMAT_RGB24 398 Rmask = 0x000000FF; 399 Gmask = 0x0000FF00; 400 Bmask = 0x00FF0000; 401#else 402 // SDL_PIXELFORMAT_BGR24 403 Rmask = 0x00FF0000; 404 Gmask = 0x0000FF00; 405 Bmask = 0x000000FF; 406#endif 407 break; 408 case 32: 409 // We don't know if this has alpha channel or not 410 correctAlpha = true; 411 // SDL_PIXELFORMAT_RGBA8888 412 Amask = 0xFF000000; 413 Rmask = 0x00FF0000; 414 Gmask = 0x0000FF00; 415 Bmask = 0x000000FF; 416 break; 417 default: 418 break; 419 } 420 break; 421 422 case BI_BITFIELDS: 423 break; // we handled this in the info header. 424 425 default: 426 break; 427 } 428 429 // Create a compatible surface, note that the colors are RGB ordered 430 { 431 SDL_PixelFormat format; 432 433 // Get the pixel format 434 format = SDL_GetPixelFormatForMasks(biBitCount, Rmask, Gmask, Bmask, Amask); 435 surface = SDL_CreateSurface(biWidth, biHeight, format); 436 437 if (!surface) { 438 goto done; 439 } 440 } 441 442 // Load the palette, if any 443 if (SDL_ISPIXELFORMAT_INDEXED(surface->format)) { 444 SDL_Palette *palette = SDL_CreateSurfacePalette(surface); 445 if (!palette) { 446 goto done; 447 } 448 449 if (SDL_SeekIO(src, fp_offset + 14 + biSize, SDL_IO_SEEK_SET) < 0) { 450 SDL_SetError("Error seeking in datastream"); 451 goto done; 452 } 453 454 if (biBitCount >= 32) { // we shift biClrUsed by this value later. 455 SDL_SetError("Unsupported or incorrect biBitCount field"); 456 goto done; 457 } 458 459 if (biClrUsed == 0) { 460 biClrUsed = 1 << biBitCount; 461 } 462 463 if (biClrUsed > (Uint32)palette->ncolors) { 464 biClrUsed = 1 << biBitCount; // try forcing it? 465 if (biClrUsed > (Uint32)palette->ncolors) { 466 SDL_SetError("Unsupported or incorrect biClrUsed field"); 467 goto done; 468 } 469 } 470 palette->ncolors = biClrUsed; 471 472 if (biSize == 12) { 473 for (i = 0; i < palette->ncolors; ++i) { 474 if (!SDL_ReadU8(src, &palette->colors[i].b) || 475 !SDL_ReadU8(src, &palette->colors[i].g) || 476 !SDL_ReadU8(src, &palette->colors[i].r)) { 477 goto done; 478 } 479 palette->colors[i].a = SDL_ALPHA_OPAQUE; 480 } 481 } else { 482 for (i = 0; i < palette->ncolors; ++i) { 483 if (!SDL_ReadU8(src, &palette->colors[i].b) || 484 !SDL_ReadU8(src, &palette->colors[i].g) || 485 !SDL_ReadU8(src, &palette->colors[i].r) || 486 !SDL_ReadU8(src, &palette->colors[i].a)) { 487 goto done; 488 } 489 490 /* According to Microsoft documentation, the fourth element 491 is reserved and must be zero, so we shouldn't treat it as 492 alpha. 493 */ 494 palette->colors[i].a = SDL_ALPHA_OPAQUE; 495 } 496 } 497 } 498 499 // Read the surface pixels. Note that the bmp image is upside down 500 if (SDL_SeekIO(src, fp_offset + bfOffBits, SDL_IO_SEEK_SET) < 0) { 501 SDL_SetError("Error seeking in datastream"); 502 goto done; 503 } 504 if ((biCompression == BI_RLE4) || (biCompression == BI_RLE8)) { 505 if (!readRlePixels(surface, src, biCompression == BI_RLE8)) { 506 SDL_SetError("Error reading from datastream"); 507 goto done; 508 } 509 510 // Success! 511 was_error = false; 512 goto done; 513 } 514 top = (Uint8 *)surface->pixels; 515 end = (Uint8 *)surface->pixels + (surface->h * surface->pitch); 516 pad = ((surface->pitch % 4) ? (4 - (surface->pitch % 4)) : 0); 517 if (topDown) { 518 bits = top; 519 } else { 520 bits = end - surface->pitch; 521 } 522 while (bits >= top && bits < end) { 523 if (SDL_ReadIO(src, bits, surface->pitch) != (size_t)surface->pitch) { 524 goto done; 525 } 526 if (biBitCount == 8 && surface->palette && biClrUsed < (1u << biBitCount)) { 527 for (i = 0; i < surface->w; ++i) { 528 if (bits[i] >= biClrUsed) { 529 SDL_SetError("A BMP image contains a pixel with a color out of the palette"); 530 goto done; 531 } 532 } 533 } 534#if SDL_BYTEORDER == SDL_BIG_ENDIAN 535 /* Byte-swap the pixels if needed. Note that the 24bpp 536 case has already been taken care of above. */ 537 switch (biBitCount) { 538 case 15: 539 case 16: 540 { 541 Uint16 *pix = (Uint16 *)bits; 542 for (i = 0; i < surface->w; i++) { 543 pix[i] = SDL_Swap16(pix[i]); 544 } 545 break; 546 } 547 548 case 32: 549 { 550 Uint32 *pix = (Uint32 *)bits; 551 for (i = 0; i < surface->w; i++) { 552 pix[i] = SDL_Swap32(pix[i]); 553 } 554 break; 555 } 556 } 557#endif 558 559 // Skip padding bytes, ugh 560 if (pad) { 561 Uint8 padbyte; 562 for (i = 0; i < pad; ++i) { 563 if (!SDL_ReadU8(src, &padbyte)) { 564 goto done; 565 } 566 } 567 } 568 if (topDown) { 569 bits += surface->pitch; 570 } else { 571 bits -= surface->pitch; 572 } 573 } 574 if (correctAlpha) { 575 CorrectAlphaChannel(surface); 576 } 577 578 was_error = false; 579 580done: 581 if (was_error) { 582 if (src) { 583 SDL_SeekIO(src, fp_offset, SDL_IO_SEEK_SET); 584 } 585 SDL_DestroySurface(surface); 586 surface = NULL; 587 } 588 if (closeio && src) { 589 SDL_CloseIO(src); 590 } 591 return surface; 592} 593 594SDL_Surface *SDL_LoadBMP(const char *file) 595{ 596 SDL_IOStream *stream = SDL_IOFromFile(file, "rb"); 597 if (!stream) { 598 return NULL; 599 } 600 return SDL_LoadBMP_IO(stream, true); 601} 602 603bool SDL_SaveBMP_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio) 604{ 605 bool was_error = true; 606 Sint64 fp_offset, new_offset; 607 int i, pad; 608 SDL_Surface *intermediate_surface = NULL; 609 Uint8 *bits; 610 bool save32bit = false; 611 bool saveLegacyBMP = false; 612 613 // The Win32 BMP file header (14 bytes) 614 char magic[2] = { 'B', 'M' }; 615 Uint32 bfSize; 616 Uint16 bfReserved1; 617 Uint16 bfReserved2; 618 Uint32 bfOffBits; 619 620 // The Win32 BITMAPINFOHEADER struct (40 bytes) 621 Uint32 biSize; 622 Sint32 biWidth; 623 Sint32 biHeight; 624 Uint16 biPlanes; 625 Uint16 biBitCount; 626 Uint32 biCompression; 627 Uint32 biSizeImage; 628 Sint32 biXPelsPerMeter; 629 Sint32 biYPelsPerMeter; 630 Uint32 biClrUsed; 631 Uint32 biClrImportant; 632 633 // The additional header members from the Win32 BITMAPV4HEADER struct (108 bytes in total) 634 Uint32 bV4RedMask = 0; 635 Uint32 bV4GreenMask = 0; 636 Uint32 bV4BlueMask = 0; 637 Uint32 bV4AlphaMask = 0; 638 Uint32 bV4CSType = 0; 639 Sint32 bV4Endpoints[3 * 3] = { 0 }; 640 Uint32 bV4GammaRed = 0; 641 Uint32 bV4GammaGreen = 0; 642 Uint32 bV4GammaBlue = 0; 643 644 // The additional header members from the Win32 BITMAPV5HEADER struct (124 bytes in total) 645 Uint32 bV5Intent = 0; 646 Uint32 bV5ProfileData = 0; 647 Uint32 bV5ProfileSize = 0; 648 Uint32 bV5Reserved = 0; 649 650 // Make sure we have somewhere to save 651 CHECK_PARAM(!SDL_SurfaceValid(surface)) { 652 SDL_InvalidParamError("surface"); 653 goto done; 654 } 655 CHECK_PARAM(!dst) { 656 SDL_InvalidParamError("dst"); 657 goto done; 658 } 659 660#ifdef SAVE_32BIT_BMP 661 // We can save alpha information in a 32-bit BMP 662 if (SDL_BITSPERPIXEL(surface->format) >= 8 && 663 (SDL_ISPIXELFORMAT_ALPHA(surface->format) || 664 surface->map.info.flags & SDL_COPY_COLORKEY)) { 665 save32bit = true; 666 } 667#endif // SAVE_32BIT_BMP 668 669 if (surface->palette && !save32bit) { 670 if (SDL_BITSPERPIXEL(surface->format) == 8) { 671 intermediate_surface = surface; 672 } else { 673 SDL_SetError("%u bpp BMP files not supported", 674 SDL_BITSPERPIXEL(surface->format)); 675 goto done; 676 } 677 } else if ((surface->format == SDL_PIXELFORMAT_BGR24 && !save32bit) || 678 (surface->format == SDL_PIXELFORMAT_BGRA32 && save32bit)) { 679 intermediate_surface = surface; 680 } else { 681 SDL_PixelFormat pixel_format; 682 683 /* If the surface has a colorkey or alpha channel we'll save a 684 32-bit BMP with alpha channel, otherwise save a 24-bit BMP. */ 685 if (save32bit) { 686 pixel_format = SDL_PIXELFORMAT_BGRA32; 687 } else { 688 pixel_format = SDL_PIXELFORMAT_BGR24; 689 } 690 intermediate_surface = SDL_ConvertSurface(surface, pixel_format); 691 if (!intermediate_surface) { 692 SDL_SetError("Couldn't convert image to %d bpp", 693 (int)SDL_BITSPERPIXEL(pixel_format)); 694 goto done; 695 } 696 } 697 698 if (save32bit) { 699 saveLegacyBMP = SDL_GetHintBoolean(SDL_HINT_BMP_SAVE_LEGACY_FORMAT, false); 700 } 701 702 if (SDL_LockSurface(intermediate_surface)) { 703 const size_t bw = intermediate_surface->w * intermediate_surface->fmt->bytes_per_pixel; 704 705 // Set the BMP file header values 706 bfSize = 0; // We'll write this when we're done 707 bfReserved1 = 0; 708 bfReserved2 = 0; 709 bfOffBits = 0; // We'll write this when we're done 710 711 // Write the BMP file header values 712 fp_offset = SDL_TellIO(dst); 713 if (fp_offset < 0) { 714 goto done; 715 } 716 if (SDL_WriteIO(dst, magic, 2) != 2 || 717 !SDL_WriteU32LE(dst, bfSize) || 718 !SDL_WriteU16LE(dst, bfReserved1) || 719 !SDL_WriteU16LE(dst, bfReserved2) || 720 !SDL_WriteU32LE(dst, bfOffBits)) { 721 goto done; 722 } 723 724 // Set the BMP info values 725 biSize = 40; 726 biWidth = intermediate_surface->w; 727 biHeight = intermediate_surface->h; 728 biPlanes = 1; 729 biBitCount = intermediate_surface->fmt->bits_per_pixel; 730 biCompression = BI_RGB; 731 biSizeImage = intermediate_surface->h * intermediate_surface->pitch; 732 biXPelsPerMeter = 0; 733 biYPelsPerMeter = 0; 734 if (intermediate_surface->palette) { 735 biClrUsed = intermediate_surface->palette->ncolors; 736 } else { 737 biClrUsed = 0; 738 } 739 biClrImportant = 0; 740 741 // Set the BMP info values 742 if (save32bit && !saveLegacyBMP) { 743 biSize = 124; 744 // Version 4 values 745 biCompression = BI_BITFIELDS; 746 // The BMP format is always little endian, these masks stay the same 747 bV4RedMask = 0x00ff0000; 748 bV4GreenMask = 0x0000ff00; 749 bV4BlueMask = 0x000000ff; 750 bV4AlphaMask = 0xff000000; 751 bV4CSType = LCS_sRGB; 752 bV4GammaRed = 0; 753 bV4GammaGreen = 0; 754 bV4GammaBlue = 0; 755 // Version 5 values 756 bV5Intent = LCS_GM_GRAPHICS; 757 bV5ProfileData = 0; 758 bV5ProfileSize = 0; 759 bV5Reserved = 0; 760 } 761 762 // Write the BMP info values 763 if (!SDL_WriteU32LE(dst, biSize) || 764 !SDL_WriteS32LE(dst, biWidth) || 765 !SDL_WriteS32LE(dst, biHeight) || 766 !SDL_WriteU16LE(dst, biPlanes) || 767 !SDL_WriteU16LE(dst, biBitCount) || 768 !SDL_WriteU32LE(dst, biCompression) || 769 !SDL_WriteU32LE(dst, biSizeImage) || 770 !SDL_WriteU32LE(dst, biXPelsPerMeter) || 771 !SDL_WriteU32LE(dst, biYPelsPerMeter) || 772 !SDL_WriteU32LE(dst, biClrUsed) || 773 !SDL_WriteU32LE(dst, biClrImportant)) { 774 goto done; 775 } 776 777 // Write the BMP info values 778 if (save32bit && !saveLegacyBMP) { 779 // Version 4 values 780 if (!SDL_WriteU32LE(dst, bV4RedMask) || 781 !SDL_WriteU32LE(dst, bV4GreenMask) || 782 !SDL_WriteU32LE(dst, bV4BlueMask) || 783 !SDL_WriteU32LE(dst, bV4AlphaMask) || 784 !SDL_WriteU32LE(dst, bV4CSType)) { 785 goto done; 786 } 787 for (i = 0; i < 3 * 3; i++) { 788 if (!SDL_WriteU32LE(dst, bV4Endpoints[i])) { 789 goto done; 790 } 791 } 792 if (!SDL_WriteU32LE(dst, bV4GammaRed) || 793 !SDL_WriteU32LE(dst, bV4GammaGreen) || 794 !SDL_WriteU32LE(dst, bV4GammaBlue)) { 795 goto done; 796 } 797 // Version 5 values 798 if (!SDL_WriteU32LE(dst, bV5Intent) || 799 !SDL_WriteU32LE(dst, bV5ProfileData) || 800 !SDL_WriteU32LE(dst, bV5ProfileSize) || 801 !SDL_WriteU32LE(dst, bV5Reserved)) { 802 goto done; 803 } 804 } 805 806 // Write the palette (in BGR color order) 807 if (intermediate_surface->palette) { 808 SDL_Color *colors; 809 int ncolors; 810 811 colors = intermediate_surface->palette->colors; 812 ncolors = intermediate_surface->palette->ncolors; 813 for (i = 0; i < ncolors; ++i) { 814 if (!SDL_WriteU8(dst, colors[i].b) || 815 !SDL_WriteU8(dst, colors[i].g) || 816 !SDL_WriteU8(dst, colors[i].r) || 817 !SDL_WriteU8(dst, colors[i].a)) { 818 goto done; 819 } 820 } 821 } 822 823 // Write the bitmap offset 824 bfOffBits = (Uint32)(SDL_TellIO(dst) - fp_offset); 825 if (SDL_SeekIO(dst, fp_offset + 10, SDL_IO_SEEK_SET) < 0) { 826 goto done; 827 } 828 if (!SDL_WriteU32LE(dst, bfOffBits)) { 829 goto done; 830 } 831 if (SDL_SeekIO(dst, fp_offset + bfOffBits, SDL_IO_SEEK_SET) < 0) { 832 goto done; 833 } 834 835 // Write the bitmap image upside down 836 bits = (Uint8 *)intermediate_surface->pixels + (intermediate_surface->h * intermediate_surface->pitch); 837 pad = ((bw % 4) ? (4 - (bw % 4)) : 0); 838 while (bits > (Uint8 *)intermediate_surface->pixels) { 839 bits -= intermediate_surface->pitch; 840 if (SDL_WriteIO(dst, bits, bw) != bw) { 841 goto done; 842 } 843 if (pad) { 844 const Uint8 padbyte = 0; 845 for (i = 0; i < pad; ++i) { 846 if (!SDL_WriteU8(dst, padbyte)) { 847 goto done; 848 } 849 } 850 } 851 } 852 853 // Write the BMP file size 854 new_offset = SDL_TellIO(dst); 855 if (new_offset < 0) { 856 goto done; 857 } 858 bfSize = (Uint32)(new_offset - fp_offset); 859 if (SDL_SeekIO(dst, fp_offset + 2, SDL_IO_SEEK_SET) < 0) { 860 goto done; 861 } 862 if (!SDL_WriteU32LE(dst, bfSize)) { 863 goto done; 864 } 865 if (SDL_SeekIO(dst, fp_offset + bfSize, SDL_IO_SEEK_SET) < 0) { 866 goto done; 867 } 868 869 // Close it up.. 870 SDL_UnlockSurface(intermediate_surface); 871 872 was_error = false; 873 } 874 875done: 876 if (intermediate_surface && intermediate_surface != surface) { 877 SDL_DestroySurface(intermediate_surface); 878 } 879 if (closeio && dst) { 880 if (!SDL_CloseIO(dst)) { 881 was_error = true; 882 } 883 } 884 if (was_error) { 885 return false; 886 } 887 return true; 888} 889 890bool SDL_SaveBMP(SDL_Surface *surface, const char *file) 891{ 892 SDL_IOStream *stream = SDL_IOFromFile(file, "wb"); 893 if (!stream) { 894 return false; 895 } 896 return SDL_SaveBMP_IO(surface, stream, true); 897} 898[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.