src/video/SDL_bmp.c
author Sam Lantinga <slouken@libsdl.org>
Tue, 15 Dec 2009 08:11:06 +0000
changeset 3565 f43c8f688f77
parent 3497 74d2f44a85de
child 3697 f7b03b6838cb
permissions -rw-r--r--
Fixed bug #906

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