Atlas - SDL_bmp.c
Home / ext / SDL / src / video Lines: 1 | Size: 29304 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2026 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 594typedef struct { 595 SDL_Surface *surface; 596 SDL_Surface *intermediate_surface; 597 bool save32bit; 598 bool saveLegacyBMP; 599} BMPSaveState; 600 601static void FreeBMPSaveState(BMPSaveState *state) 602{ 603 if (state->intermediate_surface && state->intermediate_surface != state->surface) { 604 SDL_DestroySurface(state->intermediate_surface); 605 } 606} 607 608static bool InitBMPSaveState(BMPSaveState *state, SDL_Surface *surface) 609{ 610 state->surface = surface; 611 state->intermediate_surface = NULL; 612 state->save32bit = false; 613 state->saveLegacyBMP = false; 614 615 CHECK_PARAM(!SDL_SurfaceValid(surface)) { 616 return SDL_InvalidParamError("surface"); 617 } 618 619#ifdef SAVE_32BIT_BMP 620 // We can save alpha information in a 32-bit BMP 621 if (SDL_BITSPERPIXEL(surface->format) >= 8 && 622 (SDL_ISPIXELFORMAT_ALPHA(surface->format) || 623 (surface->map.info.flags & SDL_COPY_COLORKEY))) { 624 state->save32bit = true; 625 } 626#endif // SAVE_32BIT_BMP 627 628 if (surface->palette && !state->save32bit) { 629 if (SDL_BITSPERPIXEL(surface->format) == 8) { 630 state->intermediate_surface = surface; 631 } else { 632 SDL_SetError("%u bpp BMP files not supported", 633 SDL_BITSPERPIXEL(surface->format)); 634 goto error; 635 } 636 } else if ((surface->format == SDL_PIXELFORMAT_BGR24 && !state->save32bit) || 637 (surface->format == SDL_PIXELFORMAT_BGRA32 && state->save32bit)) { 638 state->intermediate_surface = surface; 639 } else { 640 SDL_PixelFormat pixel_format; 641 642 /* If the surface has a colorkey or alpha channel we'll save a 643 32-bit BMP with alpha channel, otherwise save a 24-bit BMP. */ 644 if (state->save32bit) { 645 pixel_format = SDL_PIXELFORMAT_BGRA32; 646 } else { 647 pixel_format = SDL_PIXELFORMAT_BGR24; 648 } 649 state->intermediate_surface = SDL_ConvertSurface(surface, pixel_format); 650 if (!state->intermediate_surface) { 651 SDL_SetError("Couldn't convert image to %d bpp", 652 (int)SDL_BITSPERPIXEL(pixel_format)); 653 goto error; 654 } 655 } 656 657 if (state->save32bit) { 658 state->saveLegacyBMP = SDL_GetHintBoolean(SDL_HINT_BMP_SAVE_LEGACY_FORMAT, false); 659 } 660 return true; 661error: 662 FreeBMPSaveState(state); 663 return false; 664} 665 666SDL_Surface *SDL_LoadBMP(const char *file) 667{ 668 SDL_IOStream *stream = SDL_IOFromFile(file, "rb"); 669 if (!stream) { 670 return NULL; 671 } 672 return SDL_LoadBMP_IO(stream, true); 673} 674static bool SDL_SaveBMP_IO_Internal(BMPSaveState *state, SDL_IOStream *dst, bool closeio) 675{ 676 bool was_error = true; 677 Sint64 fp_offset, new_offset; 678 int i, pad; 679 Uint8 *bits; 680 681 // The Win32 BMP file header (14 bytes) 682 char magic[2] = { 'B', 'M' }; 683 Uint32 bfSize; 684 Uint16 bfReserved1; 685 Uint16 bfReserved2; 686 Uint32 bfOffBits; 687 688 // The Win32 BITMAPINFOHEADER struct (40 bytes) 689 Uint32 biSize; 690 Sint32 biWidth; 691 Sint32 biHeight; 692 Uint16 biPlanes; 693 Uint16 biBitCount; 694 Uint32 biCompression; 695 Uint32 biSizeImage; 696 Sint32 biXPelsPerMeter; 697 Sint32 biYPelsPerMeter; 698 Uint32 biClrUsed; 699 Uint32 biClrImportant; 700 701 // The additional header members from the Win32 BITMAPV4HEADER struct (108 bytes in total) 702 Uint32 bV4RedMask = 0; 703 Uint32 bV4GreenMask = 0; 704 Uint32 bV4BlueMask = 0; 705 Uint32 bV4AlphaMask = 0; 706 Uint32 bV4CSType = 0; 707 Sint32 bV4Endpoints[3 * 3] = { 0 }; 708 Uint32 bV4GammaRed = 0; 709 Uint32 bV4GammaGreen = 0; 710 Uint32 bV4GammaBlue = 0; 711 712 // The additional header members from the Win32 BITMAPV5HEADER struct (124 bytes in total) 713 Uint32 bV5Intent = 0; 714 Uint32 bV5ProfileData = 0; 715 Uint32 bV5ProfileSize = 0; 716 Uint32 bV5Reserved = 0; 717 718 if (SDL_LockSurface(state->intermediate_surface)) { 719 const size_t bw = state->intermediate_surface->w * state->intermediate_surface->fmt->bytes_per_pixel; 720 721 // Set the BMP file header values 722 bfSize = 0; // We'll write this when we're done 723 bfReserved1 = 0; 724 bfReserved2 = 0; 725 bfOffBits = 0; // We'll write this when we're done 726 727 // Write the BMP file header values 728 fp_offset = SDL_TellIO(dst); 729 if (fp_offset < 0) { 730 goto done; 731 } 732 if (SDL_WriteIO(dst, magic, 2) != 2 || 733 !SDL_WriteU32LE(dst, bfSize) || 734 !SDL_WriteU16LE(dst, bfReserved1) || 735 !SDL_WriteU16LE(dst, bfReserved2) || 736 !SDL_WriteU32LE(dst, bfOffBits)) { 737 goto done; 738 } 739 740 // Set the BMP info values 741 biSize = 40; 742 biWidth = state->intermediate_surface->w; 743 biHeight = state->intermediate_surface->h; 744 biPlanes = 1; 745 biBitCount = state->intermediate_surface->fmt->bits_per_pixel; 746 biCompression = BI_RGB; 747 biSizeImage = state->intermediate_surface->h * state->intermediate_surface->pitch; 748 biXPelsPerMeter = 0; 749 biYPelsPerMeter = 0; 750 if (state->intermediate_surface->palette) { 751 biClrUsed = state->intermediate_surface->palette->ncolors; 752 } else { 753 biClrUsed = 0; 754 } 755 biClrImportant = 0; 756 757 // Set the BMP info values 758 if (state->save32bit && !state->saveLegacyBMP) { 759 biSize = 124; 760 // Version 4 values 761 biCompression = BI_BITFIELDS; 762 // The BMP format is always little endian, these masks stay the same 763 bV4RedMask = 0x00ff0000; 764 bV4GreenMask = 0x0000ff00; 765 bV4BlueMask = 0x000000ff; 766 bV4AlphaMask = 0xff000000; 767 bV4CSType = LCS_sRGB; 768 bV4GammaRed = 0; 769 bV4GammaGreen = 0; 770 bV4GammaBlue = 0; 771 // Version 5 values 772 bV5Intent = LCS_GM_GRAPHICS; 773 bV5ProfileData = 0; 774 bV5ProfileSize = 0; 775 bV5Reserved = 0; 776 } 777 778 // Write the BMP info values 779 if (!SDL_WriteU32LE(dst, biSize) || 780 !SDL_WriteS32LE(dst, biWidth) || 781 !SDL_WriteS32LE(dst, biHeight) || 782 !SDL_WriteU16LE(dst, biPlanes) || 783 !SDL_WriteU16LE(dst, biBitCount) || 784 !SDL_WriteU32LE(dst, biCompression) || 785 !SDL_WriteU32LE(dst, biSizeImage) || 786 !SDL_WriteU32LE(dst, biXPelsPerMeter) || 787 !SDL_WriteU32LE(dst, biYPelsPerMeter) || 788 !SDL_WriteU32LE(dst, biClrUsed) || 789 !SDL_WriteU32LE(dst, biClrImportant)) { 790 goto done; 791 } 792 793 // Write the BMP info values 794 if (state->save32bit && !state->saveLegacyBMP) { 795 // Version 4 values 796 if (!SDL_WriteU32LE(dst, bV4RedMask) || 797 !SDL_WriteU32LE(dst, bV4GreenMask) || 798 !SDL_WriteU32LE(dst, bV4BlueMask) || 799 !SDL_WriteU32LE(dst, bV4AlphaMask) || 800 !SDL_WriteU32LE(dst, bV4CSType)) { 801 goto done; 802 } 803 for (i = 0; i < 3 * 3; i++) { 804 if (!SDL_WriteU32LE(dst, bV4Endpoints[i])) { 805 goto done; 806 } 807 } 808 if (!SDL_WriteU32LE(dst, bV4GammaRed) || 809 !SDL_WriteU32LE(dst, bV4GammaGreen) || 810 !SDL_WriteU32LE(dst, bV4GammaBlue)) { 811 goto done; 812 } 813 // Version 5 values 814 if (!SDL_WriteU32LE(dst, bV5Intent) || 815 !SDL_WriteU32LE(dst, bV5ProfileData) || 816 !SDL_WriteU32LE(dst, bV5ProfileSize) || 817 !SDL_WriteU32LE(dst, bV5Reserved)) { 818 goto done; 819 } 820 } 821 822 // Write the palette (in BGR color order) 823 if (state->intermediate_surface->palette) { 824 SDL_Color *colors; 825 int ncolors; 826 827 colors = state->intermediate_surface->palette->colors; 828 ncolors = state->intermediate_surface->palette->ncolors; 829 for (i = 0; i < ncolors; ++i) { 830 if (!SDL_WriteU8(dst, colors[i].b) || 831 !SDL_WriteU8(dst, colors[i].g) || 832 !SDL_WriteU8(dst, colors[i].r) || 833 !SDL_WriteU8(dst, colors[i].a)) { 834 goto done; 835 } 836 } 837 } 838 839 // Write the bitmap offset 840 bfOffBits = (Uint32)(SDL_TellIO(dst) - fp_offset); 841 if (SDL_SeekIO(dst, fp_offset + 10, SDL_IO_SEEK_SET) < 0) { 842 goto done; 843 } 844 if (!SDL_WriteU32LE(dst, bfOffBits)) { 845 goto done; 846 } 847 if (SDL_SeekIO(dst, fp_offset + bfOffBits, SDL_IO_SEEK_SET) < 0) { 848 goto done; 849 } 850 851 // Write the bitmap image upside down 852 bits = (Uint8 *)state->intermediate_surface->pixels + (state->intermediate_surface->h * state->intermediate_surface->pitch); 853 pad = ((bw % 4) ? (4 - (bw % 4)) : 0); 854 while (bits > (Uint8 *)state->intermediate_surface->pixels) { 855 bits -= state->intermediate_surface->pitch; 856 if (SDL_WriteIO(dst, bits, bw) != bw) { 857 goto done; 858 } 859 if (pad) { 860 const Uint8 padbyte = 0; 861 for (i = 0; i < pad; ++i) { 862 if (!SDL_WriteU8(dst, padbyte)) { 863 goto done; 864 } 865 } 866 } 867 } 868 869 // Write the BMP file size 870 new_offset = SDL_TellIO(dst); 871 if (new_offset < 0) { 872 goto done; 873 } 874 bfSize = (Uint32)(new_offset - fp_offset); 875 if (SDL_SeekIO(dst, fp_offset + 2, SDL_IO_SEEK_SET) < 0) { 876 goto done; 877 } 878 if (!SDL_WriteU32LE(dst, bfSize)) { 879 goto done; 880 } 881 if (SDL_SeekIO(dst, fp_offset + bfSize, SDL_IO_SEEK_SET) < 0) { 882 goto done; 883 } 884 885 // Close it up.. 886 SDL_UnlockSurface(state->intermediate_surface); 887 888 was_error = false; 889 } 890 891done: 892 if (closeio && dst) { 893 if (!SDL_CloseIO(dst)) { 894 was_error = true; 895 } 896 } 897 if (was_error) { 898 return false; 899 } 900 return true; 901} 902 903bool SDL_SaveBMP_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio) 904{ 905 bool result = true; 906 BMPSaveState state; 907 if (!InitBMPSaveState(&state, surface)) { 908 return false; 909 } 910 CHECK_PARAM(!dst) { 911 result = SDL_InvalidParamError("dst"); 912 goto done; 913 } 914 result = SDL_SaveBMP_IO_Internal(&state, dst, closeio); 915done: 916 FreeBMPSaveState(&state); 917 return result; 918} 919 920bool SDL_SaveBMP(SDL_Surface *surface, const char *file) 921{ 922 BMPSaveState state; 923 if (!InitBMPSaveState(&state, surface)) { 924 return false; 925 } 926 927 SDL_IOStream *stream = SDL_IOFromFile(file, "wb"); 928 if (!stream) { 929 return false; 930 } 931 932 bool result = SDL_SaveBMP_IO_Internal(&state, stream, true); 933 FreeBMPSaveState(&state); 934 return result; 935} 936[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.