1 /*
2 SDL - Simple DirectMedia Layer
3 Copyright (C) 1997-2012 Sam Lantinga
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
19 Sam Lantinga
20 slouken@libsdl.org
21 */
22 #include "LRSDL_config.h"
23
24 /*
25 Code to load and save surfaces in Windows BMP format.
26
27 Why support BMP format? Well, it's a native format for Windows, and
28 most image processing programs can read and write it. It would be nice
29 to be able to have at least one image format that we can natively load
30 and save, and since PNG is so complex that it would bloat the library,
31 BMP is a good alternative.
32
33 This code currently supports Win32 DIBs in uncompressed 8 and 24 bpp.
34 */
35
36 #include "LRSDL_video.h"
37 #include "LRSDL_endian.h"
38
39 /* Compression encodings for BMP files */
40 #ifndef BI_RGB
41 #define BI_RGB 0
42 #define BI_RLE8 1
43 #define BI_RLE4 2
44 #define BI_BITFIELDS 3
45 #endif
46
47
LRSDL_LoadBMP_RW(LRSDL_RWops * src,int freesrc)48 SDL_Surface * LRSDL_LoadBMP_RW (LRSDL_RWops *src, int freesrc)
49 {
50 LRSDL_bool was_error;
51 long fp_offset = 0;
52 int bmpPitch;
53 int i, pad;
54 SDL_Surface *surface;
55 Uint32 Rmask;
56 Uint32 Gmask;
57 Uint32 Bmask;
58 SDL_Palette *palette;
59 Uint8 *bits;
60 Uint8 *top, *end;
61 LRSDL_bool topDown;
62 int ExpandBMP;
63
64 /* The Win32 BMP file header (14 bytes) */
65 char magic[2];
66 Uint32 bfSize;
67 Uint16 bfReserved1;
68 Uint16 bfReserved2;
69 Uint32 bfOffBits;
70
71 /* The Win32 BITMAPINFOHEADER struct (40 bytes) */
72 Uint32 biSize;
73 Sint32 biWidth;
74 Sint32 biHeight;
75 Uint16 biPlanes;
76 Uint16 biBitCount;
77 Uint32 biCompression;
78 Uint32 biSizeImage;
79 Sint32 biXPelsPerMeter;
80 Sint32 biYPelsPerMeter;
81 Uint32 biClrUsed;
82 Uint32 biClrImportant;
83
84 /* Make sure we are passed a valid data source */
85 surface = NULL;
86 was_error = LRSDL_FALSE;
87 if ( src == NULL ) {
88 was_error = LRSDL_TRUE;
89 goto done;
90 }
91
92 /* Read in the BMP file header */
93 fp_offset = LRSDL_RWtell(src);
94 LRSDL_ClearError();
95 if ( LRSDL_RWread(src, magic, 1, 2) != 2 ) {
96 LRSDL_Error(SDL_EFREAD);
97 was_error = LRSDL_TRUE;
98 goto done;
99 }
100 if ( SDL_strncmp(magic, "BM", 2) != 0 ) {
101 LRSDL_SetError("File is not a Windows BMP file");
102 was_error = LRSDL_TRUE;
103 goto done;
104 }
105 bfSize = LRSDL_ReadLE32(src);
106 bfReserved1 = LRSDL_ReadLE16(src);
107 bfReserved2 = LRSDL_ReadLE16(src);
108 bfOffBits = LRSDL_ReadLE32(src);
109
110 /* Read the Win32 BITMAPINFOHEADER */
111 biSize = LRSDL_ReadLE32(src);
112 if ( biSize == 12 ) {
113 biWidth = (Uint32)LRSDL_ReadLE16(src);
114 biHeight = (Uint32)LRSDL_ReadLE16(src);
115 biPlanes = LRSDL_ReadLE16(src);
116 biBitCount = LRSDL_ReadLE16(src);
117 biCompression = BI_RGB;
118 biSizeImage = 0;
119 biXPelsPerMeter = 0;
120 biYPelsPerMeter = 0;
121 biClrUsed = 0;
122 biClrImportant = 0;
123 } else {
124 biWidth = LRSDL_ReadLE32(src);
125 biHeight = LRSDL_ReadLE32(src);
126 biPlanes = LRSDL_ReadLE16(src);
127 biBitCount = LRSDL_ReadLE16(src);
128 biCompression = LRSDL_ReadLE32(src);
129 biSizeImage = LRSDL_ReadLE32(src);
130 biXPelsPerMeter = LRSDL_ReadLE32(src);
131 biYPelsPerMeter = LRSDL_ReadLE32(src);
132 biClrUsed = LRSDL_ReadLE32(src);
133 biClrImportant = LRSDL_ReadLE32(src);
134 }
135
136 /* stop some compiler warnings. */
137 (void) bfSize;
138 (void) bfReserved1;
139 (void) bfReserved2;
140 (void) biPlanes;
141 (void) biSizeImage;
142 (void) biXPelsPerMeter;
143 (void) biYPelsPerMeter;
144 (void) biClrImportant;
145
146 if (biHeight < 0) {
147 topDown = LRSDL_TRUE;
148 biHeight = -biHeight;
149 } else {
150 topDown = LRSDL_FALSE;
151 }
152
153 /* Check for read error */
154 if ( SDL_strcmp(LRSDL_GetError(), "") != 0 ) {
155 was_error = LRSDL_TRUE;
156 goto done;
157 }
158
159 /* Expand 1 and 4 bit bitmaps to 8 bits per pixel */
160 switch (biBitCount) {
161 case 1:
162 case 4:
163 ExpandBMP = biBitCount;
164 biBitCount = 8;
165 break;
166 default:
167 ExpandBMP = 0;
168 break;
169 }
170
171 /* We don't support any BMP compression right now */
172 Rmask = Gmask = Bmask = 0;
173 switch (biCompression) {
174 case BI_RGB:
175 /* If there are no masks, use the defaults */
176 if ( bfOffBits == (14+biSize) ) {
177 /* Default values for the BMP format */
178 switch (biBitCount) {
179 case 15:
180 case 16:
181 #if defined(ABGR1555)
182 Bmask = 0x7C00;
183 Gmask = 0x03E0;
184 Rmask = 0x001F;
185 #else
186 Rmask = 0x7C00;
187 Gmask = 0x03E0;
188 Bmask = 0x001F;
189 #endif
190 break;
191 case 24:
192 #ifdef MSB_FIRST
193 Rmask = 0x000000FF;
194 Gmask = 0x0000FF00;
195 Bmask = 0x00FF0000;
196 break;
197 #endif
198 case 32:
199 #if defined(ABGR1555)
200 Bmask = 0x00FF0000;
201 Gmask = 0x0000FF00;
202 Rmask = 0x000000FF;
203 #else
204 Rmask = 0x00FF0000;
205 Gmask = 0x0000FF00;
206 Bmask = 0x000000FF;
207 #endif
208 break;
209 default:
210 break;
211 }
212 break;
213 }
214 /* Fall through -- read the RGB masks */
215
216 case BI_BITFIELDS:
217 switch (biBitCount) {
218 case 15:
219 case 16:
220 case 32:
221 Rmask = LRSDL_ReadLE32(src);
222 Gmask = LRSDL_ReadLE32(src);
223 Bmask = LRSDL_ReadLE32(src);
224 break;
225 default:
226 break;
227 }
228 break;
229 default:
230 LRSDL_SetError("Compressed BMP files not supported");
231 was_error = LRSDL_TRUE;
232 goto done;
233 }
234
235 /* Create a compatible surface, note that the colors are RGB ordered */
236 surface = LRSDL_CreateRGBSurface(SDL_SWSURFACE,
237 biWidth, biHeight, biBitCount, Rmask, Gmask, Bmask, 0);
238 if ( surface == NULL ) {
239 was_error = LRSDL_TRUE;
240 goto done;
241 }
242
243 /* Load the palette, if any */
244 palette = (surface->format)->palette;
245 if ( palette ) {
246 if ( biClrUsed == 0 ) {
247 biClrUsed = 1 << biBitCount;
248 }
249 if ( biSize == 12 ) {
250 for ( i = 0; i < (int)biClrUsed; ++i ) {
251 LRSDL_RWread(src, &palette->colors[i].b, 1, 1);
252 LRSDL_RWread(src, &palette->colors[i].g, 1, 1);
253 LRSDL_RWread(src, &palette->colors[i].r, 1, 1);
254 palette->colors[i].unused = 0;
255 }
256 } else {
257 for ( i = 0; i < (int)biClrUsed; ++i ) {
258 LRSDL_RWread(src, &palette->colors[i].b, 1, 1);
259 LRSDL_RWread(src, &palette->colors[i].g, 1, 1);
260 LRSDL_RWread(src, &palette->colors[i].r, 1, 1);
261 LRSDL_RWread(src, &palette->colors[i].unused, 1, 1);
262 }
263 }
264 palette->ncolors = biClrUsed;
265 }
266
267 /* Read the surface pixels. Note that the bmp image is upside down */
268 if ( LRSDL_RWseek(src, fp_offset+bfOffBits, RW_SEEK_SET) < 0 ) {
269 LRSDL_Error(SDL_EFSEEK);
270 was_error = LRSDL_TRUE;
271 goto done;
272 }
273 top = (Uint8 *)surface->pixels;
274 end = (Uint8 *)surface->pixels+(surface->h*surface->pitch);
275 switch (ExpandBMP) {
276 case 1:
277 bmpPitch = (biWidth + 7) >> 3;
278 pad = (((bmpPitch)%4) ? (4-((bmpPitch)%4)) : 0);
279 break;
280 case 4:
281 bmpPitch = (biWidth + 1) >> 1;
282 pad = (((bmpPitch)%4) ? (4-((bmpPitch)%4)) : 0);
283 break;
284 default:
285 pad = ((surface->pitch%4) ?
286 (4-(surface->pitch%4)) : 0);
287 break;
288 }
289 if ( topDown ) {
290 bits = top;
291 } else {
292 bits = end - surface->pitch;
293 }
294 while ( bits >= top && bits < end ) {
295 switch (ExpandBMP) {
296 case 1:
297 case 4: {
298 Uint8 pixel = 0;
299 int shift = (8-ExpandBMP);
300 for ( i=0; i<surface->w; ++i ) {
301 if ( i%(8/ExpandBMP) == 0 ) {
302 if ( !LRSDL_RWread(src, &pixel, 1, 1) ) {
303 LRSDL_SetError(
304 "Error reading from BMP");
305 was_error = LRSDL_TRUE;
306 goto done;
307 }
308 }
309 *(bits+i) = (pixel>>shift);
310 pixel <<= ExpandBMP;
311 } }
312 break;
313
314 default:
315 if ( LRSDL_RWread(src, bits, 1, surface->pitch)
316 != surface->pitch ) {
317 LRSDL_Error(SDL_EFREAD);
318 was_error = LRSDL_TRUE;
319 goto done;
320 }
321 #ifdef MSB_FIRST
322 /* Byte-swap the pixels if needed. Note that the 24bpp
323 case has already been taken care of above. */
324 switch(biBitCount) {
325 case 15:
326 case 16: {
327 Uint16 *pix = (Uint16 *)bits;
328 for(i = 0; i < surface->w; i++)
329 pix[i] = SDL_Swap16(pix[i]);
330 break;
331 }
332
333 case 32: {
334 Uint32 *pix = (Uint32 *)bits;
335 for(i = 0; i < surface->w; i++)
336 pix[i] = SDL_Swap32(pix[i]);
337 break;
338 }
339 }
340 #endif
341 break;
342 }
343 /* Skip padding bytes, ugh */
344 if ( pad ) {
345 Uint8 padbyte;
346 for ( i=0; i<pad; ++i ) {
347 LRSDL_RWread(src, &padbyte, 1, 1);
348 }
349 }
350 if ( topDown ) {
351 bits += surface->pitch;
352 } else {
353 bits -= surface->pitch;
354 }
355 }
356 done:
357 if ( was_error ) {
358 if ( src ) {
359 LRSDL_RWseek(src, fp_offset, RW_SEEK_SET);
360 }
361 if ( surface ) {
362 LRSDL_FreeSurface(surface);
363 }
364 surface = NULL;
365 }
366 if ( freesrc && src ) {
367 LRSDL_RWclose(src);
368 }
369 return(surface);
370 }
371
LRSDL_SaveBMP_RW(SDL_Surface * saveme,LRSDL_RWops * dst,int freedst)372 int LRSDL_SaveBMP_RW (SDL_Surface *saveme, LRSDL_RWops *dst, int freedst)
373 {
374 long fp_offset;
375 int i, pad;
376 SDL_Surface *surface;
377 Uint8 *bits;
378
379 /* The Win32 BMP file header (14 bytes) */
380 char magic[2] = { 'B', 'M' };
381 Uint32 bfSize;
382 Uint16 bfReserved1;
383 Uint16 bfReserved2;
384 Uint32 bfOffBits;
385
386 /* The Win32 BITMAPINFOHEADER struct (40 bytes) */
387 Uint32 biSize;
388 Sint32 biWidth;
389 Sint32 biHeight;
390 Uint16 biPlanes;
391 Uint16 biBitCount;
392 Uint32 biCompression;
393 Uint32 biSizeImage;
394 Sint32 biXPelsPerMeter;
395 Sint32 biYPelsPerMeter;
396 Uint32 biClrUsed;
397 Uint32 biClrImportant;
398
399 /* Make sure we have somewhere to save */
400 surface = NULL;
401 if ( dst ) {
402 if ( saveme->format->palette ) {
403 if ( saveme->format->BitsPerPixel == 8 ) {
404 surface = saveme;
405 } else {
406 LRSDL_SetError("%d bpp BMP files not supported",
407 saveme->format->BitsPerPixel);
408 }
409 }
410 else if ( (saveme->format->BitsPerPixel == 24) &&
411 #ifdef MSB_FIRST
412 (saveme->format->Rmask == 0x000000FF) &&
413 (saveme->format->Gmask == 0x0000FF00) &&
414 (saveme->format->Bmask == 0x00FF0000)
415 #else
416 (saveme->format->Rmask == 0x00FF0000) &&
417 (saveme->format->Gmask == 0x0000FF00) &&
418 (saveme->format->Bmask == 0x000000FF)
419 #endif
420 ) {
421 surface = saveme;
422 } else {
423 SDL_Rect bounds;
424
425 /* Convert to 24 bits per pixel */
426 surface = LRSDL_CreateRGBSurface(SDL_SWSURFACE,
427 saveme->w, saveme->h, 24,
428 #ifdef MSB_FIRST
429 0x000000FF, 0x0000FF00, 0x00FF0000,
430 #else
431 0x00FF0000, 0x0000FF00, 0x000000FF,
432 #endif
433 0);
434 if ( surface != NULL ) {
435 bounds.x = 0;
436 bounds.y = 0;
437 bounds.w = saveme->w;
438 bounds.h = saveme->h;
439 if ( LRSDL_LowerBlit(saveme, &bounds, surface,
440 &bounds) < 0 ) {
441 LRSDL_FreeSurface(surface);
442 LRSDL_SetError(
443 "Couldn't convert image to 24 bpp");
444 surface = NULL;
445 }
446 }
447 }
448 }
449
450 if ( surface )
451 {
452 const int bw = surface->w*surface->format->BytesPerPixel;
453
454 /* Set the BMP file header values */
455 bfSize = 0; /* We'll write this when we're done */
456 bfReserved1 = 0;
457 bfReserved2 = 0;
458 bfOffBits = 0; /* We'll write this when we're done */
459
460 /* Write the BMP file header values */
461 fp_offset = LRSDL_RWtell(dst);
462 LRSDL_ClearError();
463 LRSDL_RWwrite(dst, magic, 2, 1);
464 LRSDL_WriteLE32(dst, bfSize);
465 LRSDL_WriteLE16(dst, bfReserved1);
466 LRSDL_WriteLE16(dst, bfReserved2);
467 LRSDL_WriteLE32(dst, bfOffBits);
468
469 /* Set the BMP info values */
470 biSize = 40;
471 biWidth = surface->w;
472 biHeight = surface->h;
473 biPlanes = 1;
474 biBitCount = surface->format->BitsPerPixel;
475 biCompression = BI_RGB;
476 biSizeImage = surface->h*surface->pitch;
477 biXPelsPerMeter = 0;
478 biYPelsPerMeter = 0;
479 if ( surface->format->palette ) {
480 biClrUsed = surface->format->palette->ncolors;
481 } else {
482 biClrUsed = 0;
483 }
484 biClrImportant = 0;
485
486 /* Write the BMP info values */
487 LRSDL_WriteLE32(dst, biSize);
488 LRSDL_WriteLE32(dst, biWidth);
489 LRSDL_WriteLE32(dst, biHeight);
490 LRSDL_WriteLE16(dst, biPlanes);
491 LRSDL_WriteLE16(dst, biBitCount);
492 LRSDL_WriteLE32(dst, biCompression);
493 LRSDL_WriteLE32(dst, biSizeImage);
494 LRSDL_WriteLE32(dst, biXPelsPerMeter);
495 LRSDL_WriteLE32(dst, biYPelsPerMeter);
496 LRSDL_WriteLE32(dst, biClrUsed);
497 LRSDL_WriteLE32(dst, biClrImportant);
498
499 /* Write the palette (in BGR color order) */
500 if ( surface->format->palette ) {
501 SDL_Color *colors;
502 int ncolors;
503
504 colors = surface->format->palette->colors;
505 ncolors = surface->format->palette->ncolors;
506 for ( i=0; i<ncolors; ++i ) {
507 LRSDL_RWwrite(dst, &colors[i].b, 1, 1);
508 LRSDL_RWwrite(dst, &colors[i].g, 1, 1);
509 LRSDL_RWwrite(dst, &colors[i].r, 1, 1);
510 LRSDL_RWwrite(dst, &colors[i].unused, 1, 1);
511 }
512 }
513
514 /* Write the bitmap offset */
515 bfOffBits = LRSDL_RWtell(dst)-fp_offset;
516 if ( LRSDL_RWseek(dst, fp_offset+10, RW_SEEK_SET) < 0 ) {
517 LRSDL_Error(SDL_EFSEEK);
518 }
519 LRSDL_WriteLE32(dst, bfOffBits);
520 if ( LRSDL_RWseek(dst, fp_offset+bfOffBits, RW_SEEK_SET) < 0 ) {
521 LRSDL_Error(SDL_EFSEEK);
522 }
523
524 /* Write the bitmap image upside down */
525 bits = (Uint8 *)surface->pixels+(surface->h*surface->pitch);
526 pad = ((bw%4) ? (4-(bw%4)) : 0);
527 while ( bits > (Uint8 *)surface->pixels ) {
528 bits -= surface->pitch;
529 if ( LRSDL_RWwrite(dst, bits, 1, bw) != bw) {
530 LRSDL_Error(SDL_EFWRITE);
531 break;
532 }
533 if ( pad ) {
534 const Uint8 padbyte = 0;
535 for ( i=0; i<pad; ++i ) {
536 LRSDL_RWwrite(dst, &padbyte, 1, 1);
537 }
538 }
539 }
540
541 /* Write the BMP file size */
542 bfSize = LRSDL_RWtell(dst)-fp_offset;
543 if ( LRSDL_RWseek(dst, fp_offset+2, RW_SEEK_SET) < 0 ) {
544 LRSDL_Error(SDL_EFSEEK);
545 }
546 LRSDL_WriteLE32(dst, bfSize);
547 if ( LRSDL_RWseek(dst, fp_offset+bfSize, RW_SEEK_SET) < 0 ) {
548 LRSDL_Error(SDL_EFSEEK);
549 }
550
551 if ( surface != saveme ) {
552 LRSDL_FreeSurface(surface);
553 }
554 }
555
556 if ( freedst && dst ) {
557 LRSDL_RWclose(dst);
558 }
559 return((SDL_strcmp(LRSDL_GetError(), "") == 0) ? 0 : -1);
560 }
561