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