src/video/SDL_bmp.c
author Sam Lantinga
Sat, 19 Sep 2009 13:29:40 +0000
changeset 3280 00cace2d9080
parent 3026 69ab1117dd3b
child 3310 b907e83deb88
permissions -rw-r--r--
Merged a cleaned up version of Jiang's code changes from Google Summer of Code 2009
     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     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 ((int) 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 ((int) 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         SDL_bool save32bit = SDL_FALSE;
   381 #ifdef SAVE_32BIT_BMP
   382         /* We can save alpha information in a 32-bit BMP */
   383         if (saveme->map->info.flags & SDL_COPY_COLORKEY ||
   384             saveme->format->Amask) {
   385             save32bit = SDL_TRUE;
   386         }
   387 #endif /* SAVE_32BIT_BMP */
   388 
   389         if (saveme->format->palette && !save32bit) {
   390             if (saveme->format->BitsPerPixel == 8) {
   391                 surface = saveme;
   392             } else {
   393                 SDL_SetError("%d bpp BMP files not supported",
   394                              saveme->format->BitsPerPixel);
   395             }
   396         } else if ((saveme->format->BitsPerPixel == 24) &&
   397 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
   398                    (saveme->format->Rmask == 0x00FF0000) &&
   399                    (saveme->format->Gmask == 0x0000FF00) &&
   400                    (saveme->format->Bmask == 0x000000FF)
   401 #else
   402                    (saveme->format->Rmask == 0x000000FF) &&
   403                    (saveme->format->Gmask == 0x0000FF00) &&
   404                    (saveme->format->Bmask == 0x00FF0000)
   405 #endif
   406             ) {
   407             surface = saveme;
   408         } else {
   409             SDL_PixelFormat format;
   410 
   411             /* If the surface has a colorkey or alpha channel we'll save a
   412                32-bit BMP with alpha channel, otherwise save a 24-bit BMP. */
   413             if (save32bit) {
   414                 SDL_InitFormat(&format, 32,
   415                                0x00FF0000, 0x0000FF00, 0x000000FF,
   416                                0xFF000000);
   417             } else {
   418                 SDL_InitFormat(&format, 24,
   419 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
   420                                0x00FF0000, 0x0000FF00, 0x000000FF,
   421 #else
   422                                0x000000FF, 0x0000FF00, 0x00FF0000,
   423 #endif
   424                                0);
   425             }
   426             surface = SDL_ConvertSurface(saveme, &format, 0);
   427             if (!surface) {
   428                 SDL_SetError("Couldn't convert image to %d bpp",
   429                              format.BitsPerPixel);
   430             }
   431         }
   432     }
   433 
   434     if (surface && (SDL_LockSurface(surface) == 0)) {
   435         const int bw = surface->w * surface->format->BytesPerPixel;
   436 
   437         /* Set the BMP file header values */
   438         bfSize = 0;             /* We'll write this when we're done */
   439         bfReserved1 = 0;
   440         bfReserved2 = 0;
   441         bfOffBits = 0;          /* We'll write this when we're done */
   442 
   443         /* Write the BMP file header values */
   444         fp_offset = SDL_RWtell(dst);
   445         SDL_ClearError();
   446         SDL_RWwrite(dst, magic, 2, 1);
   447         SDL_WriteLE32(dst, bfSize);
   448         SDL_WriteLE16(dst, bfReserved1);
   449         SDL_WriteLE16(dst, bfReserved2);
   450         SDL_WriteLE32(dst, bfOffBits);
   451 
   452         /* Set the BMP info values */
   453         biSize = 40;
   454         biWidth = surface->w;
   455         biHeight = surface->h;
   456         biPlanes = 1;
   457         biBitCount = surface->format->BitsPerPixel;
   458         biCompression = BI_RGB;
   459         biSizeImage = surface->h * surface->pitch;
   460         biXPelsPerMeter = 0;
   461         biYPelsPerMeter = 0;
   462         if (surface->format->palette) {
   463             biClrUsed = surface->format->palette->ncolors;
   464         } else {
   465             biClrUsed = 0;
   466         }
   467         biClrImportant = 0;
   468 
   469         /* Write the BMP info values */
   470         SDL_WriteLE32(dst, biSize);
   471         SDL_WriteLE32(dst, biWidth);
   472         SDL_WriteLE32(dst, biHeight);
   473         SDL_WriteLE16(dst, biPlanes);
   474         SDL_WriteLE16(dst, biBitCount);
   475         SDL_WriteLE32(dst, biCompression);
   476         SDL_WriteLE32(dst, biSizeImage);
   477         SDL_WriteLE32(dst, biXPelsPerMeter);
   478         SDL_WriteLE32(dst, biYPelsPerMeter);
   479         SDL_WriteLE32(dst, biClrUsed);
   480         SDL_WriteLE32(dst, biClrImportant);
   481 
   482         /* Write the palette (in BGR color order) */
   483         if (surface->format->palette) {
   484             SDL_Color *colors;
   485             int ncolors;
   486 
   487             colors = surface->format->palette->colors;
   488             ncolors = surface->format->palette->ncolors;
   489             for (i = 0; i < ncolors; ++i) {
   490                 SDL_RWwrite(dst, &colors[i].b, 1, 1);
   491                 SDL_RWwrite(dst, &colors[i].g, 1, 1);
   492                 SDL_RWwrite(dst, &colors[i].r, 1, 1);
   493                 SDL_RWwrite(dst, &colors[i].unused, 1, 1);
   494             }
   495         }
   496 
   497         /* Write the bitmap offset */
   498         bfOffBits = SDL_RWtell(dst) - fp_offset;
   499         if (SDL_RWseek(dst, fp_offset + 10, RW_SEEK_SET) < 0) {
   500             SDL_Error(SDL_EFSEEK);
   501         }
   502         SDL_WriteLE32(dst, bfOffBits);
   503         if (SDL_RWseek(dst, fp_offset + bfOffBits, RW_SEEK_SET) < 0) {
   504             SDL_Error(SDL_EFSEEK);
   505         }
   506 
   507         /* Write the bitmap image upside down */
   508         bits = (Uint8 *) surface->pixels + (surface->h * surface->pitch);
   509         pad = ((bw % 4) ? (4 - (bw % 4)) : 0);
   510         while (bits > (Uint8 *) surface->pixels) {
   511             bits -= surface->pitch;
   512             if (SDL_RWwrite(dst, bits, 1, bw) != bw) {
   513                 SDL_Error(SDL_EFWRITE);
   514                 break;
   515             }
   516             if (pad) {
   517                 const Uint8 padbyte = 0;
   518                 for (i = 0; i < pad; ++i) {
   519                     SDL_RWwrite(dst, &padbyte, 1, 1);
   520                 }
   521             }
   522         }
   523 
   524         /* Write the BMP file size */
   525         bfSize = SDL_RWtell(dst) - fp_offset;
   526         if (SDL_RWseek(dst, fp_offset + 2, RW_SEEK_SET) < 0) {
   527             SDL_Error(SDL_EFSEEK);
   528         }
   529         SDL_WriteLE32(dst, bfSize);
   530         if (SDL_RWseek(dst, fp_offset + bfSize, RW_SEEK_SET) < 0) {
   531             SDL_Error(SDL_EFSEEK);
   532         }
   533 
   534         /* Close it up.. */
   535         SDL_UnlockSurface(surface);
   536         if (surface != saveme) {
   537             SDL_FreeSurface(surface);
   538         }
   539     }
   540 
   541     if (freedst && dst) {
   542         SDL_RWclose(dst);
   543     }
   544     return ((SDL_strcmp(SDL_GetError(), "") == 0) ? 0 : -1);
   545 }
   546 
   547 /* vi: set ts=4 sw=4 expandtab: */