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