1 | /* |
2 | Simple DirectMedia Layer |
3 | Copyright (C) 1997-2021 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_hints.h" |
36 | #include "SDL_video.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 | /* Logical color space values for BMP files */ |
51 | #ifndef LCS_WINDOWS_COLOR_SPACE |
52 | /* 0x57696E20 == "Win " */ |
53 | #define LCS_WINDOWS_COLOR_SPACE 0x57696E20 |
54 | #endif |
55 | |
56 | static int readRlePixels(SDL_Surface * surface, SDL_RWops * src, int isRle8) |
57 | { |
58 | /* |
59 | | Sets the surface pixels from src. A bmp image is upside down. |
60 | */ |
61 | int pitch = surface->pitch; |
62 | int height = surface->h; |
63 | Uint8 *start = (Uint8 *)surface->pixels; |
64 | Uint8 *end = start + (height*pitch); |
65 | Uint8 *bits = end-pitch, *spot; |
66 | int ofs = 0; |
67 | Uint8 ch; |
68 | Uint8 needsPad; |
69 | |
70 | #define COPY_PIXEL(x) spot = &bits[ofs++]; if(spot >= start && spot < end) *spot = (x) |
71 | |
72 | for (;;) { |
73 | if (!SDL_RWread(src, &ch, 1, 1)) return 1; |
74 | /* |
75 | | encoded mode starts with a run length, and then a byte |
76 | | with two colour indexes to alternate between for the run |
77 | */ |
78 | if (ch) { |
79 | Uint8 pixel; |
80 | if (!SDL_RWread(src, &pixel, 1, 1)) return 1; |
81 | if (isRle8) { /* 256-color bitmap, compressed */ |
82 | do { |
83 | COPY_PIXEL(pixel); |
84 | } while (--ch); |
85 | } else { /* 16-color bitmap, compressed */ |
86 | Uint8 pixel0 = pixel >> 4; |
87 | Uint8 pixel1 = pixel & 0x0F; |
88 | for (;;) { |
89 | COPY_PIXEL(pixel0); /* even count, high nibble */ |
90 | if (!--ch) break; |
91 | COPY_PIXEL(pixel1); /* odd count, low nibble */ |
92 | if (!--ch) break; |
93 | } |
94 | } |
95 | } else { |
96 | /* |
97 | | A leading zero is an escape; it may signal the end of the bitmap, |
98 | | a cursor move, or some absolute data. |
99 | | zero tag may be absolute mode or an escape |
100 | */ |
101 | if (!SDL_RWread(src, &ch, 1, 1)) return 1; |
102 | switch (ch) { |
103 | case 0: /* end of line */ |
104 | ofs = 0; |
105 | bits -= pitch; /* go to previous */ |
106 | break; |
107 | case 1: /* end of bitmap */ |
108 | return 0; /* success! */ |
109 | case 2: /* delta */ |
110 | if (!SDL_RWread(src, &ch, 1, 1)) return 1; |
111 | ofs += ch; |
112 | if (!SDL_RWread(src, &ch, 1, 1)) return 1; |
113 | bits -= (ch * pitch); |
114 | break; |
115 | default: /* no compression */ |
116 | if (isRle8) { |
117 | needsPad = (ch & 1); |
118 | do { |
119 | Uint8 pixel; |
120 | if (!SDL_RWread(src, &pixel, 1, 1)) return 1; |
121 | COPY_PIXEL(pixel); |
122 | } while (--ch); |
123 | } else { |
124 | needsPad = (((ch+1)>>1) & 1); /* (ch+1)>>1: bytes size */ |
125 | for (;;) { |
126 | Uint8 pixel; |
127 | if (!SDL_RWread(src, &pixel, 1, 1)) return 1; |
128 | COPY_PIXEL(pixel >> 4); |
129 | if (!--ch) break; |
130 | COPY_PIXEL(pixel & 0x0F); |
131 | if (!--ch) break; |
132 | } |
133 | } |
134 | /* pad at even boundary */ |
135 | if (needsPad && !SDL_RWread(src, &ch, 1, 1)) return 1; |
136 | break; |
137 | } |
138 | } |
139 | } |
140 | } |
141 | |
142 | static void CorrectAlphaChannel(SDL_Surface *surface) |
143 | { |
144 | /* Check to see if there is any alpha channel data */ |
145 | SDL_bool hasAlpha = SDL_FALSE; |
146 | #if SDL_BYTEORDER == SDL_BIG_ENDIAN |
147 | int alphaChannelOffset = 0; |
148 | #else |
149 | int alphaChannelOffset = 3; |
150 | #endif |
151 | Uint8 *alpha = ((Uint8*)surface->pixels) + alphaChannelOffset; |
152 | Uint8 *end = alpha + surface->h * surface->pitch; |
153 | |
154 | while (alpha < end) { |
155 | if (*alpha != 0) { |
156 | hasAlpha = SDL_TRUE; |
157 | break; |
158 | } |
159 | alpha += 4; |
160 | } |
161 | |
162 | if (!hasAlpha) { |
163 | alpha = ((Uint8*)surface->pixels) + alphaChannelOffset; |
164 | while (alpha < end) { |
165 | *alpha = SDL_ALPHA_OPAQUE; |
166 | alpha += 4; |
167 | } |
168 | } |
169 | } |
170 | |
171 | SDL_Surface * |
172 | SDL_LoadBMP_RW(SDL_RWops * src, int freesrc) |
173 | { |
174 | SDL_bool was_error; |
175 | Sint64 fp_offset = 0; |
176 | int bmpPitch; |
177 | int i, pad; |
178 | SDL_Surface *surface; |
179 | Uint32 Rmask = 0; |
180 | Uint32 Gmask = 0; |
181 | Uint32 Bmask = 0; |
182 | Uint32 Amask = 0; |
183 | SDL_Palette *palette; |
184 | Uint8 *bits; |
185 | Uint8 *top, *end; |
186 | SDL_bool topDown; |
187 | int ExpandBMP; |
188 | SDL_bool haveRGBMasks = SDL_FALSE; |
189 | SDL_bool haveAlphaMask = SDL_FALSE; |
190 | SDL_bool correctAlpha = SDL_FALSE; |
191 | |
192 | /* The Win32 BMP file header (14 bytes) */ |
193 | char magic[2]; |
194 | /* Uint32 bfSize; */ |
195 | /* Uint16 bfReserved1; */ |
196 | /* Uint16 bfReserved2; */ |
197 | Uint32 bfOffBits; |
198 | |
199 | /* The Win32 BITMAPINFOHEADER struct (40 bytes) */ |
200 | Uint32 biSize; |
201 | Sint32 biWidth = 0; |
202 | Sint32 biHeight = 0; |
203 | /* Uint16 biPlanes; */ |
204 | Uint16 biBitCount = 0; |
205 | Uint32 biCompression = 0; |
206 | /* Uint32 biSizeImage; */ |
207 | /* Sint32 biXPelsPerMeter; */ |
208 | /* Sint32 biYPelsPerMeter; */ |
209 | Uint32 biClrUsed = 0; |
210 | /* Uint32 biClrImportant; */ |
211 | |
212 | /* Make sure we are passed a valid data source */ |
213 | surface = NULL; |
214 | was_error = SDL_FALSE; |
215 | if (src == NULL) { |
216 | was_error = SDL_TRUE; |
217 | goto done; |
218 | } |
219 | |
220 | /* Read in the BMP file header */ |
221 | fp_offset = SDL_RWtell(src); |
222 | SDL_ClearError(); |
223 | if (SDL_RWread(src, magic, 1, 2) != 2) { |
224 | SDL_Error(SDL_EFREAD); |
225 | was_error = SDL_TRUE; |
226 | goto done; |
227 | } |
228 | if (SDL_strncmp(magic, "BM" , 2) != 0) { |
229 | SDL_SetError("File is not a Windows BMP file" ); |
230 | was_error = SDL_TRUE; |
231 | goto done; |
232 | } |
233 | /* bfSize = */ SDL_ReadLE32(src); |
234 | /* bfReserved1 = */ SDL_ReadLE16(src); |
235 | /* bfReserved2 = */ SDL_ReadLE16(src); |
236 | bfOffBits = SDL_ReadLE32(src); |
237 | |
238 | /* Read the Win32 BITMAPINFOHEADER */ |
239 | biSize = SDL_ReadLE32(src); |
240 | if (biSize == 12) { /* really old BITMAPCOREHEADER */ |
241 | biWidth = (Uint32) SDL_ReadLE16(src); |
242 | biHeight = (Uint32) SDL_ReadLE16(src); |
243 | /* biPlanes = */ SDL_ReadLE16(src); |
244 | biBitCount = SDL_ReadLE16(src); |
245 | biCompression = BI_RGB; |
246 | /* biSizeImage = 0; */ |
247 | /* biXPelsPerMeter = 0; */ |
248 | /* biYPelsPerMeter = 0; */ |
249 | biClrUsed = 0; |
250 | /* biClrImportant = 0; */ |
251 | } else if (biSize >= 40) { /* some version of BITMAPINFOHEADER */ |
252 | Uint32 ; |
253 | biWidth = SDL_ReadLE32(src); |
254 | biHeight = SDL_ReadLE32(src); |
255 | /* biPlanes = */ SDL_ReadLE16(src); |
256 | biBitCount = SDL_ReadLE16(src); |
257 | biCompression = SDL_ReadLE32(src); |
258 | /* biSizeImage = */ SDL_ReadLE32(src); |
259 | /* biXPelsPerMeter = */ SDL_ReadLE32(src); |
260 | /* biYPelsPerMeter = */ SDL_ReadLE32(src); |
261 | biClrUsed = SDL_ReadLE32(src); |
262 | /* biClrImportant = */ SDL_ReadLE32(src); |
263 | |
264 | /* 64 == BITMAPCOREHEADER2, an incompatible OS/2 2.x extension. Skip this stuff for now. */ |
265 | if (biSize != 64) { |
266 | /* This is complicated. If compression is BI_BITFIELDS, then |
267 | we have 3 DWORDS that specify the RGB masks. This is either |
268 | stored here in an BITMAPV2INFOHEADER (which only differs in |
269 | that it adds these RGB masks) and biSize >= 52, or we've got |
270 | these masks stored in the exact same place, but strictly |
271 | speaking, this is the bmiColors field in BITMAPINFO immediately |
272 | following the legacy v1 info header, just past biSize. */ |
273 | if (biCompression == BI_BITFIELDS) { |
274 | haveRGBMasks = SDL_TRUE; |
275 | Rmask = SDL_ReadLE32(src); |
276 | Gmask = SDL_ReadLE32(src); |
277 | Bmask = SDL_ReadLE32(src); |
278 | |
279 | /* ...v3 adds an alpha mask. */ |
280 | if (biSize >= 56) { /* BITMAPV3INFOHEADER; adds alpha mask */ |
281 | haveAlphaMask = SDL_TRUE; |
282 | Amask = SDL_ReadLE32(src); |
283 | } |
284 | } else { |
285 | /* the mask fields are ignored for v2+ headers if not BI_BITFIELD. */ |
286 | if (biSize >= 52) { /* BITMAPV2INFOHEADER; adds RGB masks */ |
287 | /*Rmask = */ SDL_ReadLE32(src); |
288 | /*Gmask = */ SDL_ReadLE32(src); |
289 | /*Bmask = */ SDL_ReadLE32(src); |
290 | } |
291 | if (biSize >= 56) { /* BITMAPV3INFOHEADER; adds alpha mask */ |
292 | /*Amask = */ SDL_ReadLE32(src); |
293 | } |
294 | } |
295 | |
296 | /* Insert other fields here; Wikipedia and MSDN say we're up to |
297 | v5 of this header, but we ignore those for now (they add gamma, |
298 | color spaces, etc). Ignoring the weird OS/2 2.x format, we |
299 | currently parse up to v3 correctly (hopefully!). */ |
300 | } |
301 | |
302 | /* skip any header bytes we didn't handle... */ |
303 | headerSize = (Uint32) (SDL_RWtell(src) - (fp_offset + 14)); |
304 | if (biSize > headerSize) { |
305 | SDL_RWseek(src, (biSize - headerSize), RW_SEEK_CUR); |
306 | } |
307 | } |
308 | if (biWidth <= 0 || biHeight == 0) { |
309 | SDL_SetError("BMP file with bad dimensions (%" SDL_PRIs32 "x%" SDL_PRIs32 ")" , biWidth, biHeight); |
310 | was_error = SDL_TRUE; |
311 | goto done; |
312 | } |
313 | if (biHeight < 0) { |
314 | topDown = SDL_TRUE; |
315 | biHeight = -biHeight; |
316 | } else { |
317 | topDown = SDL_FALSE; |
318 | } |
319 | |
320 | /* Check for read error */ |
321 | if (SDL_strcmp(SDL_GetError(), "" ) != 0) { |
322 | was_error = SDL_TRUE; |
323 | goto done; |
324 | } |
325 | |
326 | /* Expand 1 and 4 bit bitmaps to 8 bits per pixel */ |
327 | switch (biBitCount) { |
328 | case 1: |
329 | case 4: |
330 | ExpandBMP = biBitCount; |
331 | biBitCount = 8; |
332 | break; |
333 | case 0: |
334 | case 2: |
335 | case 3: |
336 | case 5: |
337 | case 6: |
338 | case 7: |
339 | SDL_SetError("%d-bpp BMP images are not supported" , biBitCount); |
340 | was_error = SDL_TRUE; |
341 | goto done; |
342 | default: |
343 | ExpandBMP = 0; |
344 | break; |
345 | } |
346 | |
347 | /* RLE4 and RLE8 BMP compression is supported */ |
348 | switch (biCompression) { |
349 | case BI_RGB: |
350 | /* If there are no masks, use the defaults */ |
351 | SDL_assert(!haveRGBMasks); |
352 | SDL_assert(!haveAlphaMask); |
353 | /* Default values for the BMP format */ |
354 | switch (biBitCount) { |
355 | case 15: |
356 | case 16: |
357 | Rmask = 0x7C00; |
358 | Gmask = 0x03E0; |
359 | Bmask = 0x001F; |
360 | break; |
361 | case 24: |
362 | #if SDL_BYTEORDER == SDL_BIG_ENDIAN |
363 | Rmask = 0x000000FF; |
364 | Gmask = 0x0000FF00; |
365 | Bmask = 0x00FF0000; |
366 | #else |
367 | Rmask = 0x00FF0000; |
368 | Gmask = 0x0000FF00; |
369 | Bmask = 0x000000FF; |
370 | #endif |
371 | break; |
372 | case 32: |
373 | /* We don't know if this has alpha channel or not */ |
374 | correctAlpha = SDL_TRUE; |
375 | Amask = 0xFF000000; |
376 | Rmask = 0x00FF0000; |
377 | Gmask = 0x0000FF00; |
378 | Bmask = 0x000000FF; |
379 | break; |
380 | default: |
381 | break; |
382 | } |
383 | break; |
384 | |
385 | case BI_BITFIELDS: |
386 | break; /* we handled this in the info header. */ |
387 | |
388 | default: |
389 | break; |
390 | } |
391 | |
392 | /* Create a compatible surface, note that the colors are RGB ordered */ |
393 | surface = |
394 | SDL_CreateRGBSurface(0, biWidth, biHeight, biBitCount, Rmask, Gmask, |
395 | Bmask, Amask); |
396 | if (surface == NULL) { |
397 | was_error = SDL_TRUE; |
398 | goto done; |
399 | } |
400 | |
401 | /* Load the palette, if any */ |
402 | palette = (surface->format)->palette; |
403 | if (palette) { |
404 | if (SDL_RWseek(src, fp_offset+14+biSize, RW_SEEK_SET) < 0) { |
405 | SDL_Error(SDL_EFSEEK); |
406 | was_error = SDL_TRUE; |
407 | goto done; |
408 | } |
409 | |
410 | /* |
411 | | guich: always use 1<<bpp b/c some bitmaps can bring wrong information |
412 | | for colorsUsed |
413 | */ |
414 | /* if (biClrUsed == 0) { */ |
415 | biClrUsed = 1 << biBitCount; |
416 | /* } */ |
417 | if (biSize == 12) { |
418 | for (i = 0; i < (int) biClrUsed; ++i) { |
419 | SDL_RWread(src, &palette->colors[i].b, 1, 1); |
420 | SDL_RWread(src, &palette->colors[i].g, 1, 1); |
421 | SDL_RWread(src, &palette->colors[i].r, 1, 1); |
422 | palette->colors[i].a = SDL_ALPHA_OPAQUE; |
423 | } |
424 | } else { |
425 | for (i = 0; i < (int) biClrUsed; ++i) { |
426 | SDL_RWread(src, &palette->colors[i].b, 1, 1); |
427 | SDL_RWread(src, &palette->colors[i].g, 1, 1); |
428 | SDL_RWread(src, &palette->colors[i].r, 1, 1); |
429 | SDL_RWread(src, &palette->colors[i].a, 1, 1); |
430 | |
431 | /* According to Microsoft documentation, the fourth element |
432 | is reserved and must be zero, so we shouldn't treat it as |
433 | alpha. |
434 | */ |
435 | palette->colors[i].a = SDL_ALPHA_OPAQUE; |
436 | } |
437 | } |
438 | palette->ncolors = biClrUsed; |
439 | } |
440 | |
441 | /* Read the surface pixels. Note that the bmp image is upside down */ |
442 | if (SDL_RWseek(src, fp_offset + bfOffBits, RW_SEEK_SET) < 0) { |
443 | SDL_Error(SDL_EFSEEK); |
444 | was_error = SDL_TRUE; |
445 | goto done; |
446 | } |
447 | if ((biCompression == BI_RLE4) || (biCompression == BI_RLE8)) { |
448 | was_error = (SDL_bool)readRlePixels(surface, src, biCompression == BI_RLE8); |
449 | if (was_error) SDL_SetError("Error reading from BMP" ); |
450 | goto done; |
451 | } |
452 | top = (Uint8 *)surface->pixels; |
453 | end = (Uint8 *)surface->pixels+(surface->h*surface->pitch); |
454 | switch (ExpandBMP) { |
455 | case 1: |
456 | bmpPitch = (biWidth + 7) >> 3; |
457 | pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0); |
458 | break; |
459 | case 4: |
460 | bmpPitch = (biWidth + 1) >> 1; |
461 | pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0); |
462 | break; |
463 | default: |
464 | pad = ((surface->pitch % 4) ? (4 - (surface->pitch % 4)) : 0); |
465 | break; |
466 | } |
467 | if (topDown) { |
468 | bits = top; |
469 | } else { |
470 | bits = end - surface->pitch; |
471 | } |
472 | while (bits >= top && bits < end) { |
473 | switch (ExpandBMP) { |
474 | case 1: |
475 | case 4:{ |
476 | Uint8 pixel = 0; |
477 | int shift = (8 - ExpandBMP); |
478 | for (i = 0; i < surface->w; ++i) { |
479 | if (i % (8 / ExpandBMP) == 0) { |
480 | if (!SDL_RWread(src, &pixel, 1, 1)) { |
481 | SDL_SetError("Error reading from BMP" ); |
482 | was_error = SDL_TRUE; |
483 | goto done; |
484 | } |
485 | } |
486 | bits[i] = (pixel >> shift); |
487 | if (bits[i] >= biClrUsed) { |
488 | SDL_SetError("A BMP image contains a pixel with a color out of the palette" ); |
489 | was_error = SDL_TRUE; |
490 | goto done; |
491 | } |
492 | pixel <<= ExpandBMP; |
493 | } |
494 | } |
495 | break; |
496 | |
497 | default: |
498 | if (SDL_RWread(src, bits, 1, surface->pitch) != surface->pitch) { |
499 | SDL_Error(SDL_EFREAD); |
500 | was_error = SDL_TRUE; |
501 | goto done; |
502 | } |
503 | if (biBitCount == 8 && palette && biClrUsed < (1u << biBitCount)) { |
504 | for (i = 0; i < surface->w; ++i) { |
505 | if (bits[i] >= biClrUsed) { |
506 | SDL_SetError("A BMP image contains a pixel with a color out of the palette" ); |
507 | was_error = SDL_TRUE; |
508 | goto done; |
509 | } |
510 | } |
511 | } |
512 | #if SDL_BYTEORDER == SDL_BIG_ENDIAN |
513 | /* Byte-swap the pixels if needed. Note that the 24bpp |
514 | case has already been taken care of above. */ |
515 | switch (biBitCount) { |
516 | case 15: |
517 | case 16:{ |
518 | Uint16 *pix = (Uint16 *) bits; |
519 | for (i = 0; i < surface->w; i++) |
520 | pix[i] = SDL_Swap16(pix[i]); |
521 | break; |
522 | } |
523 | |
524 | case 32:{ |
525 | Uint32 *pix = (Uint32 *) bits; |
526 | for (i = 0; i < surface->w; i++) |
527 | pix[i] = SDL_Swap32(pix[i]); |
528 | break; |
529 | } |
530 | } |
531 | #endif |
532 | break; |
533 | } |
534 | /* Skip padding bytes, ugh */ |
535 | if (pad) { |
536 | Uint8 padbyte; |
537 | for (i = 0; i < pad; ++i) { |
538 | SDL_RWread(src, &padbyte, 1, 1); |
539 | } |
540 | } |
541 | if (topDown) { |
542 | bits += surface->pitch; |
543 | } else { |
544 | bits -= surface->pitch; |
545 | } |
546 | } |
547 | if (correctAlpha) { |
548 | CorrectAlphaChannel(surface); |
549 | } |
550 | done: |
551 | if (was_error) { |
552 | if (src) { |
553 | SDL_RWseek(src, fp_offset, RW_SEEK_SET); |
554 | } |
555 | if (surface) { |
556 | SDL_FreeSurface(surface); |
557 | } |
558 | surface = NULL; |
559 | } |
560 | if (freesrc && src) { |
561 | SDL_RWclose(src); |
562 | } |
563 | return (surface); |
564 | } |
565 | |
566 | int |
567 | SDL_SaveBMP_RW(SDL_Surface * saveme, SDL_RWops * dst, int freedst) |
568 | { |
569 | Sint64 fp_offset; |
570 | int i, pad; |
571 | SDL_Surface *surface; |
572 | Uint8 *bits; |
573 | SDL_bool save32bit = SDL_FALSE; |
574 | SDL_bool saveLegacyBMP = SDL_FALSE; |
575 | |
576 | /* The Win32 BMP file header (14 bytes) */ |
577 | char magic[2] = { 'B', 'M' }; |
578 | Uint32 bfSize; |
579 | Uint16 bfReserved1; |
580 | Uint16 bfReserved2; |
581 | Uint32 bfOffBits; |
582 | |
583 | /* The Win32 BITMAPINFOHEADER struct (40 bytes) */ |
584 | Uint32 biSize; |
585 | Sint32 biWidth; |
586 | Sint32 biHeight; |
587 | Uint16 biPlanes; |
588 | Uint16 biBitCount; |
589 | Uint32 biCompression; |
590 | Uint32 biSizeImage; |
591 | Sint32 biXPelsPerMeter; |
592 | Sint32 biYPelsPerMeter; |
593 | Uint32 biClrUsed; |
594 | Uint32 biClrImportant; |
595 | |
596 | /* The additional header members from the Win32 BITMAPV4HEADER struct (108 bytes in total) */ |
597 | Uint32 bV4RedMask = 0; |
598 | Uint32 bV4GreenMask = 0; |
599 | Uint32 bV4BlueMask = 0; |
600 | Uint32 bV4AlphaMask = 0; |
601 | Uint32 bV4CSType = 0; |
602 | Sint32 bV4Endpoints[3 * 3] = {0}; |
603 | Uint32 bV4GammaRed = 0; |
604 | Uint32 bV4GammaGreen = 0; |
605 | Uint32 bV4GammaBlue = 0; |
606 | |
607 | /* Make sure we have somewhere to save */ |
608 | surface = NULL; |
609 | if (dst) { |
610 | #ifdef SAVE_32BIT_BMP |
611 | /* We can save alpha information in a 32-bit BMP */ |
612 | if (saveme->format->BitsPerPixel >= 8 && (saveme->format->Amask || |
613 | saveme->map->info.flags & SDL_COPY_COLORKEY)) { |
614 | save32bit = SDL_TRUE; |
615 | } |
616 | #endif /* SAVE_32BIT_BMP */ |
617 | |
618 | if (saveme->format->palette && !save32bit) { |
619 | if (saveme->format->BitsPerPixel == 8) { |
620 | surface = saveme; |
621 | } else { |
622 | SDL_SetError("%d bpp BMP files not supported" , |
623 | saveme->format->BitsPerPixel); |
624 | } |
625 | } else if ((saveme->format->BitsPerPixel == 24) && !save32bit && |
626 | #if SDL_BYTEORDER == SDL_LIL_ENDIAN |
627 | (saveme->format->Rmask == 0x00FF0000) && |
628 | (saveme->format->Gmask == 0x0000FF00) && |
629 | (saveme->format->Bmask == 0x000000FF) |
630 | #else |
631 | (saveme->format->Rmask == 0x000000FF) && |
632 | (saveme->format->Gmask == 0x0000FF00) && |
633 | (saveme->format->Bmask == 0x00FF0000) |
634 | #endif |
635 | ) { |
636 | surface = saveme; |
637 | } else { |
638 | SDL_PixelFormat format; |
639 | |
640 | /* If the surface has a colorkey or alpha channel we'll save a |
641 | 32-bit BMP with alpha channel, otherwise save a 24-bit BMP. */ |
642 | if (save32bit) { |
643 | SDL_InitFormat(&format, SDL_PIXELFORMAT_BGRA32); |
644 | } else { |
645 | SDL_InitFormat(&format, SDL_PIXELFORMAT_BGR24); |
646 | } |
647 | surface = SDL_ConvertSurface(saveme, &format, 0); |
648 | if (!surface) { |
649 | SDL_SetError("Couldn't convert image to %d bpp" , |
650 | format.BitsPerPixel); |
651 | } |
652 | } |
653 | } else { |
654 | /* Set no error here because it may overwrite a more useful message from |
655 | SDL_RWFromFile() if SDL_SaveBMP_RW() is called from SDL_SaveBMP(). */ |
656 | return -1; |
657 | } |
658 | |
659 | if (save32bit) { |
660 | saveLegacyBMP = SDL_GetHintBoolean(SDL_HINT_BMP_SAVE_LEGACY_FORMAT, SDL_FALSE); |
661 | } |
662 | |
663 | if (surface && (SDL_LockSurface(surface) == 0)) { |
664 | const int bw = surface->w * surface->format->BytesPerPixel; |
665 | |
666 | /* Set the BMP file header values */ |
667 | bfSize = 0; /* We'll write this when we're done */ |
668 | bfReserved1 = 0; |
669 | bfReserved2 = 0; |
670 | bfOffBits = 0; /* We'll write this when we're done */ |
671 | |
672 | /* Write the BMP file header values */ |
673 | fp_offset = SDL_RWtell(dst); |
674 | SDL_ClearError(); |
675 | SDL_RWwrite(dst, magic, 2, 1); |
676 | SDL_WriteLE32(dst, bfSize); |
677 | SDL_WriteLE16(dst, bfReserved1); |
678 | SDL_WriteLE16(dst, bfReserved2); |
679 | SDL_WriteLE32(dst, bfOffBits); |
680 | |
681 | /* Set the BMP info values */ |
682 | biSize = 40; |
683 | biWidth = surface->w; |
684 | biHeight = surface->h; |
685 | biPlanes = 1; |
686 | biBitCount = surface->format->BitsPerPixel; |
687 | biCompression = BI_RGB; |
688 | biSizeImage = surface->h * surface->pitch; |
689 | biXPelsPerMeter = 0; |
690 | biYPelsPerMeter = 0; |
691 | if (surface->format->palette) { |
692 | biClrUsed = surface->format->palette->ncolors; |
693 | } else { |
694 | biClrUsed = 0; |
695 | } |
696 | biClrImportant = 0; |
697 | |
698 | /* Set the BMP info values for the version 4 header */ |
699 | if (save32bit && !saveLegacyBMP) { |
700 | biSize = 108; |
701 | biCompression = BI_BITFIELDS; |
702 | /* The BMP format is always little endian, these masks stay the same */ |
703 | bV4RedMask = 0x00ff0000; |
704 | bV4GreenMask = 0x0000ff00; |
705 | bV4BlueMask = 0x000000ff; |
706 | bV4AlphaMask = 0xff000000; |
707 | bV4CSType = LCS_WINDOWS_COLOR_SPACE; |
708 | bV4GammaRed = 0; |
709 | bV4GammaGreen = 0; |
710 | bV4GammaBlue = 0; |
711 | } |
712 | |
713 | /* Write the BMP info values */ |
714 | SDL_WriteLE32(dst, biSize); |
715 | SDL_WriteLE32(dst, biWidth); |
716 | SDL_WriteLE32(dst, biHeight); |
717 | SDL_WriteLE16(dst, biPlanes); |
718 | SDL_WriteLE16(dst, biBitCount); |
719 | SDL_WriteLE32(dst, biCompression); |
720 | SDL_WriteLE32(dst, biSizeImage); |
721 | SDL_WriteLE32(dst, biXPelsPerMeter); |
722 | SDL_WriteLE32(dst, biYPelsPerMeter); |
723 | SDL_WriteLE32(dst, biClrUsed); |
724 | SDL_WriteLE32(dst, biClrImportant); |
725 | |
726 | /* Write the BMP info values for the version 4 header */ |
727 | if (save32bit && !saveLegacyBMP) { |
728 | SDL_WriteLE32(dst, bV4RedMask); |
729 | SDL_WriteLE32(dst, bV4GreenMask); |
730 | SDL_WriteLE32(dst, bV4BlueMask); |
731 | SDL_WriteLE32(dst, bV4AlphaMask); |
732 | SDL_WriteLE32(dst, bV4CSType); |
733 | for (i = 0; i < 3 * 3; i++) { |
734 | SDL_WriteLE32(dst, bV4Endpoints[i]); |
735 | } |
736 | SDL_WriteLE32(dst, bV4GammaRed); |
737 | SDL_WriteLE32(dst, bV4GammaGreen); |
738 | SDL_WriteLE32(dst, bV4GammaBlue); |
739 | } |
740 | |
741 | /* Write the palette (in BGR color order) */ |
742 | if (surface->format->palette) { |
743 | SDL_Color *colors; |
744 | int ncolors; |
745 | |
746 | colors = surface->format->palette->colors; |
747 | ncolors = surface->format->palette->ncolors; |
748 | for (i = 0; i < ncolors; ++i) { |
749 | SDL_RWwrite(dst, &colors[i].b, 1, 1); |
750 | SDL_RWwrite(dst, &colors[i].g, 1, 1); |
751 | SDL_RWwrite(dst, &colors[i].r, 1, 1); |
752 | SDL_RWwrite(dst, &colors[i].a, 1, 1); |
753 | } |
754 | } |
755 | |
756 | /* Write the bitmap offset */ |
757 | bfOffBits = (Uint32)(SDL_RWtell(dst) - fp_offset); |
758 | if (SDL_RWseek(dst, fp_offset + 10, RW_SEEK_SET) < 0) { |
759 | SDL_Error(SDL_EFSEEK); |
760 | } |
761 | SDL_WriteLE32(dst, bfOffBits); |
762 | if (SDL_RWseek(dst, fp_offset + bfOffBits, RW_SEEK_SET) < 0) { |
763 | SDL_Error(SDL_EFSEEK); |
764 | } |
765 | |
766 | /* Write the bitmap image upside down */ |
767 | bits = (Uint8 *) surface->pixels + (surface->h * surface->pitch); |
768 | pad = ((bw % 4) ? (4 - (bw % 4)) : 0); |
769 | while (bits > (Uint8 *) surface->pixels) { |
770 | bits -= surface->pitch; |
771 | if (SDL_RWwrite(dst, bits, 1, bw) != bw) { |
772 | SDL_Error(SDL_EFWRITE); |
773 | break; |
774 | } |
775 | if (pad) { |
776 | const Uint8 padbyte = 0; |
777 | for (i = 0; i < pad; ++i) { |
778 | SDL_RWwrite(dst, &padbyte, 1, 1); |
779 | } |
780 | } |
781 | } |
782 | |
783 | /* Write the BMP file size */ |
784 | bfSize = (Uint32)(SDL_RWtell(dst) - fp_offset); |
785 | if (SDL_RWseek(dst, fp_offset + 2, RW_SEEK_SET) < 0) { |
786 | SDL_Error(SDL_EFSEEK); |
787 | } |
788 | SDL_WriteLE32(dst, bfSize); |
789 | if (SDL_RWseek(dst, fp_offset + bfSize, RW_SEEK_SET) < 0) { |
790 | SDL_Error(SDL_EFSEEK); |
791 | } |
792 | |
793 | /* Close it up.. */ |
794 | SDL_UnlockSurface(surface); |
795 | if (surface != saveme) { |
796 | SDL_FreeSurface(surface); |
797 | } |
798 | } |
799 | |
800 | if (freedst && dst) { |
801 | SDL_RWclose(dst); |
802 | } |
803 | return ((SDL_strcmp(SDL_GetError(), "" ) == 0) ? 0 : -1); |
804 | } |
805 | |
806 | /* vi: set ts=4 sw=4 expandtab: */ |
807 | |