SDL-1.2.14
[sdl_omap.git] / src / video / SDL_bmp.c
1 /*
2     SDL - Simple DirectMedia Layer
3     Copyright (C) 1997-2009 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 "SDL_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 "SDL_video.h"
37 #include "SDL_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
48 SDL_Surface * SDL_LoadBMP_RW (SDL_RWops *src, int freesrc)
49 {
50         SDL_bool was_error;
51         long fp_offset;
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         SDL_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 = SDL_FALSE;
87         if ( src == NULL ) {
88                 was_error = SDL_TRUE;
89                 goto done;
90         }
91
92         /* Read in the BMP file header */
93         fp_offset = SDL_RWtell(src);
94         SDL_ClearError();
95         if ( SDL_RWread(src, magic, 1, 2) != 2 ) {
96                 SDL_Error(SDL_EFREAD);
97                 was_error = SDL_TRUE;
98                 goto done;
99         }
100         if ( SDL_strncmp(magic, "BM", 2) != 0 ) {
101                 SDL_SetError("File is not a Windows BMP file");
102                 was_error = SDL_TRUE;
103                 goto done;
104         }
105         bfSize          = SDL_ReadLE32(src);
106         bfReserved1     = SDL_ReadLE16(src);
107         bfReserved2     = SDL_ReadLE16(src);
108         bfOffBits       = SDL_ReadLE32(src);
109
110         /* Read the Win32 BITMAPINFOHEADER */
111         biSize          = SDL_ReadLE32(src);
112         if ( biSize == 12 ) {
113                 biWidth         = (Uint32)SDL_ReadLE16(src);
114                 biHeight        = (Uint32)SDL_ReadLE16(src);
115                 biPlanes        = SDL_ReadLE16(src);
116                 biBitCount      = SDL_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         = SDL_ReadLE32(src);
125                 biHeight        = SDL_ReadLE32(src);
126                 biPlanes        = SDL_ReadLE16(src);
127                 biBitCount      = SDL_ReadLE16(src);
128                 biCompression   = SDL_ReadLE32(src);
129                 biSizeImage     = SDL_ReadLE32(src);
130                 biXPelsPerMeter = SDL_ReadLE32(src);
131                 biYPelsPerMeter = SDL_ReadLE32(src);
132                 biClrUsed       = SDL_ReadLE32(src);
133                 biClrImportant  = SDL_ReadLE32(src);
134         }
135         if (biHeight < 0) {
136                 topDown = SDL_TRUE;
137                 biHeight = -biHeight;
138         } else {
139                 topDown = SDL_FALSE;
140         }
141
142         /* Check for read error */
143         if ( SDL_strcmp(SDL_GetError(), "") != 0 ) {
144                 was_error = SDL_TRUE;
145                 goto done;
146         }
147
148         /* Expand 1 and 4 bit bitmaps to 8 bits per pixel */
149         switch (biBitCount) {
150                 case 1:
151                 case 4:
152                         ExpandBMP = biBitCount;
153                         biBitCount = 8;
154                         break;
155                 default:
156                         ExpandBMP = 0;
157                         break;
158         }
159
160         /* We don't support any BMP compression right now */
161         Rmask = Gmask = Bmask = 0;
162         switch (biCompression) {
163                 case BI_RGB:
164                         /* If there are no masks, use the defaults */
165                         if ( bfOffBits == (14+biSize) ) {
166                                 /* Default values for the BMP format */
167                                 switch (biBitCount) {
168                                         case 15:
169                                         case 16:
170                                                 Rmask = 0x7C00;
171                                                 Gmask = 0x03E0;
172                                                 Bmask = 0x001F;
173                                                 break;
174                                         case 24:
175 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
176                                                 Rmask = 0x000000FF;
177                                                 Gmask = 0x0000FF00;
178                                                 Bmask = 0x00FF0000;
179                                                 break;
180 #endif
181                                         case 32:
182                                                 Rmask = 0x00FF0000;
183                                                 Gmask = 0x0000FF00;
184                                                 Bmask = 0x000000FF;
185                                                 break;
186                                         default:
187                                                 break;
188                                 }
189                                 break;
190                         }
191                         /* Fall through -- read the RGB masks */
192
193                 case BI_BITFIELDS:
194                         switch (biBitCount) {
195                                 case 15:
196                                 case 16:
197                                 case 32:
198                                         Rmask = SDL_ReadLE32(src);
199                                         Gmask = SDL_ReadLE32(src);
200                                         Bmask = SDL_ReadLE32(src);
201                                         break;
202                                 default:
203                                         break;
204                         }
205                         break;
206                 default:
207                         SDL_SetError("Compressed BMP files not supported");
208                         was_error = SDL_TRUE;
209                         goto done;
210         }
211
212         /* Create a compatible surface, note that the colors are RGB ordered */
213         surface = SDL_CreateRGBSurface(SDL_SWSURFACE,
214                         biWidth, biHeight, biBitCount, Rmask, Gmask, Bmask, 0);
215         if ( surface == NULL ) {
216                 was_error = SDL_TRUE;
217                 goto done;
218         }
219
220         /* Load the palette, if any */
221         palette = (surface->format)->palette;
222         if ( palette ) {
223                 if ( biClrUsed == 0 ) {
224                         biClrUsed = 1 << biBitCount;
225                 }
226                 if ( biSize == 12 ) {
227                         for ( i = 0; i < (int)biClrUsed; ++i ) {
228                                 SDL_RWread(src, &palette->colors[i].b, 1, 1);
229                                 SDL_RWread(src, &palette->colors[i].g, 1, 1);
230                                 SDL_RWread(src, &palette->colors[i].r, 1, 1);
231                                 palette->colors[i].unused = 0;
232                         }       
233                 } else {
234                         for ( i = 0; i < (int)biClrUsed; ++i ) {
235                                 SDL_RWread(src, &palette->colors[i].b, 1, 1);
236                                 SDL_RWread(src, &palette->colors[i].g, 1, 1);
237                                 SDL_RWread(src, &palette->colors[i].r, 1, 1);
238                                 SDL_RWread(src, &palette->colors[i].unused, 1, 1);
239                         }       
240                 }
241                 palette->ncolors = biClrUsed;
242         }
243
244         /* Read the surface pixels.  Note that the bmp image is upside down */
245         if ( SDL_RWseek(src, fp_offset+bfOffBits, RW_SEEK_SET) < 0 ) {
246                 SDL_Error(SDL_EFSEEK);
247                 was_error = SDL_TRUE;
248                 goto done;
249         }
250         top = (Uint8 *)surface->pixels;
251         end = (Uint8 *)surface->pixels+(surface->h*surface->pitch);
252         switch (ExpandBMP) {
253                 case 1:
254                         bmpPitch = (biWidth + 7) >> 3;
255                         pad  = (((bmpPitch)%4) ? (4-((bmpPitch)%4)) : 0);
256                         break;
257                 case 4:
258                         bmpPitch = (biWidth + 1) >> 1;
259                         pad  = (((bmpPitch)%4) ? (4-((bmpPitch)%4)) : 0);
260                         break;
261                 default:
262                         pad  = ((surface->pitch%4) ?
263                                         (4-(surface->pitch%4)) : 0);
264                         break;
265         }
266         if ( topDown ) {
267                 bits = top;
268         } else {
269                 bits = end - surface->pitch;
270         }
271         while ( bits >= top && bits < end ) {
272                 switch (ExpandBMP) {
273                         case 1:
274                         case 4: {
275                         Uint8 pixel = 0;
276                         int   shift = (8-ExpandBMP);
277                         for ( i=0; i<surface->w; ++i ) {
278                                 if ( i%(8/ExpandBMP) == 0 ) {
279                                         if ( !SDL_RWread(src, &pixel, 1, 1) ) {
280                                                 SDL_SetError(
281                                         "Error reading from BMP");
282                                                 was_error = SDL_TRUE;
283                                                 goto done;
284                                         }
285                                 }
286                                 *(bits+i) = (pixel>>shift);
287                                 pixel <<= ExpandBMP;
288                         } }
289                         break;
290
291                         default:
292                         if ( SDL_RWread(src, bits, 1, surface->pitch)
293                                                          != surface->pitch ) {
294                                 SDL_Error(SDL_EFREAD);
295                                 was_error = SDL_TRUE;
296                                 goto done;
297                         }
298 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
299                         /* Byte-swap the pixels if needed. Note that the 24bpp
300                            case has already been taken care of above. */
301                         switch(biBitCount) {
302                                 case 15:
303                                 case 16: {
304                                         Uint16 *pix = (Uint16 *)bits;
305                                         for(i = 0; i < surface->w; i++)
306                                                 pix[i] = SDL_Swap16(pix[i]);
307                                         break;
308                                 }
309
310                                 case 32: {
311                                         Uint32 *pix = (Uint32 *)bits;
312                                         for(i = 0; i < surface->w; i++)
313                                                 pix[i] = SDL_Swap32(pix[i]);
314                                         break;
315                                 }
316                         }
317 #endif
318                         break;
319                 }
320                 /* Skip padding bytes, ugh */
321                 if ( pad ) {
322                         Uint8 padbyte;
323                         for ( i=0; i<pad; ++i ) {
324                                 SDL_RWread(src, &padbyte, 1, 1);
325                         }
326                 }
327                 if ( topDown ) {
328                         bits += surface->pitch;
329                 } else {
330                         bits -= surface->pitch;
331                 }
332         }
333 done:
334         if ( was_error ) {
335                 if ( src ) {
336                         SDL_RWseek(src, fp_offset, RW_SEEK_SET);
337                 }
338                 if ( surface ) {
339                         SDL_FreeSurface(surface);
340                 }
341                 surface = NULL;
342         }
343         if ( freesrc && src ) {
344                 SDL_RWclose(src);
345         }
346         return(surface);
347 }
348
349 int SDL_SaveBMP_RW (SDL_Surface *saveme, SDL_RWops *dst, int freedst)
350 {
351         long fp_offset;
352         int i, pad;
353         SDL_Surface *surface;
354         Uint8 *bits;
355
356         /* The Win32 BMP file header (14 bytes) */
357         char   magic[2] = { 'B', 'M' };
358         Uint32 bfSize;
359         Uint16 bfReserved1;
360         Uint16 bfReserved2;
361         Uint32 bfOffBits;
362
363         /* The Win32 BITMAPINFOHEADER struct (40 bytes) */
364         Uint32 biSize;
365         Sint32 biWidth;
366         Sint32 biHeight;
367         Uint16 biPlanes;
368         Uint16 biBitCount;
369         Uint32 biCompression;
370         Uint32 biSizeImage;
371         Sint32 biXPelsPerMeter;
372         Sint32 biYPelsPerMeter;
373         Uint32 biClrUsed;
374         Uint32 biClrImportant;
375
376         /* Make sure we have somewhere to save */
377         surface = NULL;
378         if ( dst ) {
379                 if ( saveme->format->palette ) {
380                         if ( saveme->format->BitsPerPixel == 8 ) {
381                                 surface = saveme;
382                         } else {
383                                 SDL_SetError("%d bpp BMP files not supported",
384                                                 saveme->format->BitsPerPixel);
385                         }
386                 }
387                 else if ( (saveme->format->BitsPerPixel == 24) &&
388 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
389                                 (saveme->format->Rmask == 0x00FF0000) &&
390                                 (saveme->format->Gmask == 0x0000FF00) &&
391                                 (saveme->format->Bmask == 0x000000FF)
392 #else
393                                 (saveme->format->Rmask == 0x000000FF) &&
394                                 (saveme->format->Gmask == 0x0000FF00) &&
395                                 (saveme->format->Bmask == 0x00FF0000)
396 #endif
397                           ) {
398                         surface = saveme;
399                 } else {
400                         SDL_Rect bounds;
401
402                         /* Convert to 24 bits per pixel */
403                         surface = SDL_CreateRGBSurface(SDL_SWSURFACE,
404                                         saveme->w, saveme->h, 24,
405 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
406                                         0x00FF0000, 0x0000FF00, 0x000000FF,
407 #else
408                                         0x000000FF, 0x0000FF00, 0x00FF0000,
409 #endif
410                                         0);
411                         if ( surface != NULL ) {
412                                 bounds.x = 0;
413                                 bounds.y = 0;
414                                 bounds.w = saveme->w;
415                                 bounds.h = saveme->h;
416                                 if ( SDL_LowerBlit(saveme, &bounds, surface,
417                                                         &bounds) < 0 ) {
418                                         SDL_FreeSurface(surface);
419                                         SDL_SetError(
420                                         "Couldn't convert image to 24 bpp");
421                                         surface = NULL;
422                                 }
423                         }
424                 }
425         }
426
427         if ( surface && (SDL_LockSurface(surface) == 0) ) {
428                 const int bw = surface->w*surface->format->BytesPerPixel;
429
430                 /* Set the BMP file header values */
431                 bfSize = 0;              /* We'll write this when we're done */
432                 bfReserved1 = 0;
433                 bfReserved2 = 0;
434                 bfOffBits = 0;          /* We'll write this when we're done */
435
436                 /* Write the BMP file header values */
437                 fp_offset = SDL_RWtell(dst);
438                 SDL_ClearError();
439                 SDL_RWwrite(dst, magic, 2, 1);
440                 SDL_WriteLE32(dst, bfSize);
441                 SDL_WriteLE16(dst, bfReserved1);
442                 SDL_WriteLE16(dst, bfReserved2);
443                 SDL_WriteLE32(dst, bfOffBits);
444
445                 /* Set the BMP info values */
446                 biSize = 40;
447                 biWidth = surface->w;
448                 biHeight = surface->h;
449                 biPlanes = 1;
450                 biBitCount = surface->format->BitsPerPixel;
451                 biCompression = BI_RGB;
452                 biSizeImage = surface->h*surface->pitch;
453                 biXPelsPerMeter = 0;
454                 biYPelsPerMeter = 0;
455                 if ( surface->format->palette ) {
456                         biClrUsed = surface->format->palette->ncolors;
457                 } else {
458                         biClrUsed = 0;
459                 }
460                 biClrImportant = 0;
461
462                 /* Write the BMP info values */
463                 SDL_WriteLE32(dst, biSize);
464                 SDL_WriteLE32(dst, biWidth);
465                 SDL_WriteLE32(dst, biHeight);
466                 SDL_WriteLE16(dst, biPlanes);
467                 SDL_WriteLE16(dst, biBitCount);
468                 SDL_WriteLE32(dst, biCompression);
469                 SDL_WriteLE32(dst, biSizeImage);
470                 SDL_WriteLE32(dst, biXPelsPerMeter);
471                 SDL_WriteLE32(dst, biYPelsPerMeter);
472                 SDL_WriteLE32(dst, biClrUsed);
473                 SDL_WriteLE32(dst, biClrImportant);
474
475                 /* Write the palette (in BGR color order) */
476                 if ( surface->format->palette ) {
477                         SDL_Color *colors;
478                         int       ncolors;
479
480                         colors = surface->format->palette->colors;
481                         ncolors = surface->format->palette->ncolors;
482                         for ( i=0; i<ncolors; ++i ) {
483                                 SDL_RWwrite(dst, &colors[i].b, 1, 1);
484                                 SDL_RWwrite(dst, &colors[i].g, 1, 1);
485                                 SDL_RWwrite(dst, &colors[i].r, 1, 1);
486                                 SDL_RWwrite(dst, &colors[i].unused, 1, 1);
487                         }
488                 }
489
490                 /* Write the bitmap offset */
491                 bfOffBits = SDL_RWtell(dst)-fp_offset;
492                 if ( SDL_RWseek(dst, fp_offset+10, RW_SEEK_SET) < 0 ) {
493                         SDL_Error(SDL_EFSEEK);
494                 }
495                 SDL_WriteLE32(dst, bfOffBits);
496                 if ( SDL_RWseek(dst, fp_offset+bfOffBits, RW_SEEK_SET) < 0 ) {
497                         SDL_Error(SDL_EFSEEK);
498                 }
499
500                 /* Write the bitmap image upside down */
501                 bits = (Uint8 *)surface->pixels+(surface->h*surface->pitch);
502                 pad  = ((bw%4) ? (4-(bw%4)) : 0);
503                 while ( bits > (Uint8 *)surface->pixels ) {
504                         bits -= surface->pitch;
505                         if ( SDL_RWwrite(dst, bits, 1, bw) != bw) {
506                                 SDL_Error(SDL_EFWRITE);
507                                 break;
508                         }
509                         if ( pad ) {
510                                 const Uint8 padbyte = 0;
511                                 for ( i=0; i<pad; ++i ) {
512                                         SDL_RWwrite(dst, &padbyte, 1, 1);
513                                 }
514                         }
515                 }
516
517                 /* Write the BMP file size */
518                 bfSize = SDL_RWtell(dst)-fp_offset;
519                 if ( SDL_RWseek(dst, fp_offset+2, RW_SEEK_SET) < 0 ) {
520                         SDL_Error(SDL_EFSEEK);
521                 }
522                 SDL_WriteLE32(dst, bfSize);
523                 if ( SDL_RWseek(dst, fp_offset+bfSize, RW_SEEK_SET) < 0 ) {
524                         SDL_Error(SDL_EFSEEK);
525                 }
526
527                 /* Close it up.. */
528                 SDL_UnlockSurface(surface);
529                 if ( surface != saveme ) {
530                         SDL_FreeSurface(surface);
531                 }
532         }
533
534         if ( freedst && dst ) {
535                 SDL_RWclose(dst);
536         }
537         return((SDL_strcmp(SDL_GetError(), "") == 0) ? 0 : -1);
538 }