src/video/SDL_bmp.c
author Sam Lantinga <slouken@libsdl.org>
Sat, 18 May 2013 14:17:52 -0700
changeset 7191 75360622e65f
parent 7068 1fa727447de3
child 7375 c8b16b3a3c9b
permissions -rw-r--r--
File style cleanup for the SDL 2.0 release
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2013 Sam Lantinga <slouken@libsdl.org>
     4 
     5   This software is provided 'as-is', without any express or implied
     6   warranty.  In no event will the authors be held liable for any damages
     7   arising from the use of this software.
     8 
     9   Permission is granted to anyone to use this software for any purpose,
    10   including commercial applications, and to alter it and redistribute it
    11   freely, subject to the following restrictions:
    12 
    13   1. The origin of this software must not be misrepresented; you must not
    14      claim that you wrote the original software. If you use this software
    15      in a product, an acknowledgment in the product documentation would be
    16      appreciated but is not required.
    17   2. Altered source versions must be plainly marked as such, and must not be
    18      misrepresented as being the original software.
    19   3. This notice may not be removed or altered from any source distribution.
    20 */
    21 #include "SDL_config.h"
    22 
    23 /*
    24    Code to load and save surfaces in Windows BMP format.
    25 
    26    Why support BMP format?  Well, it's a native format for Windows, and
    27    most image processing programs can read and write it.  It would be nice
    28    to be able to have at least one image format that we can natively load
    29    and save, and since PNG is so complex that it would bloat the library,
    30    BMP is a good alternative.
    31 
    32    This code currently supports Win32 DIBs in uncompressed 8 and 24 bpp.
    33 */
    34 
    35 #include "SDL_video.h"
    36 #include "SDL_endian.h"
    37 #include "SDL_pixels_c.h"
    38 
    39 #define SAVE_32BIT_BMP
    40 
    41 /* Compression encodings for BMP files */
    42 #ifndef BI_RGB
    43 #define BI_RGB      0
    44 #define BI_RLE8     1
    45 #define BI_RLE4     2
    46 #define BI_BITFIELDS    3
    47 #endif
    48 
    49 
    50 SDL_Surface *
    51 SDL_LoadBMP_RW(SDL_RWops * src, int freesrc)
    52 {
    53     SDL_bool was_error;
    54     Sint64 fp_offset = 0;
    55     int bmpPitch;
    56     int i, pad;
    57     SDL_Surface *surface;
    58     Uint32 Rmask;
    59     Uint32 Gmask;
    60     Uint32 Bmask;
    61     Uint32 Amask;
    62     SDL_Palette *palette;
    63     Uint8 *bits;
    64     Uint8 *top, *end;
    65     SDL_bool topDown;
    66     int ExpandBMP;
    67 
    68     /* The Win32 BMP file header (14 bytes) */
    69     char magic[2];
    70     /*Uint32 bfSize = 0;*/
    71     /*Uint16 bfReserved1 = 0;*/
    72     /*Uint16 bfReserved2 = 0;*/
    73     Uint32 bfOffBits = 0;
    74 
    75     /* The Win32 BITMAPINFOHEADER struct (40 bytes) */
    76     Uint32 biSize = 0;
    77     Sint32 biWidth = 0;
    78     Sint32 biHeight = 0;
    79     /*Uint16 biPlanes = 0;*/
    80     Uint16 biBitCount = 0;
    81     Uint32 biCompression = 0;
    82     /*Uint32 biSizeImage = 0;*/
    83     /*Sint32 biXPelsPerMeter = 0;*/
    84     /*Sint32 biYPelsPerMeter = 0;*/
    85     Uint32 biClrUsed = 0;
    86     /*Uint32 biClrImportant = 0;*/
    87 
    88     /* Make sure we are passed a valid data source */
    89     surface = NULL;
    90     was_error = SDL_FALSE;
    91     if (src == NULL) {
    92         was_error = SDL_TRUE;
    93         goto done;
    94     }
    95 
    96     /* Read in the BMP file header */
    97     fp_offset = SDL_RWtell(src);
    98     SDL_ClearError();
    99     if (SDL_RWread(src, magic, 1, 2) != 2) {
   100         SDL_Error(SDL_EFREAD);
   101         was_error = SDL_TRUE;
   102         goto done;
   103     }
   104     if (SDL_strncmp(magic, "BM", 2) != 0) {
   105         SDL_SetError("File is not a Windows BMP file");
   106         was_error = SDL_TRUE;
   107         goto done;
   108     }
   109     /*bfSize =*/ SDL_ReadLE32(src);
   110     /*bfReserved1 =*/ SDL_ReadLE16(src);
   111     /*bfReserved2 =*/ SDL_ReadLE16(src);
   112     bfOffBits = SDL_ReadLE32(src);
   113 
   114     /* Read the Win32 BITMAPINFOHEADER */
   115     biSize = SDL_ReadLE32(src);
   116     if (biSize == 12) {
   117         biWidth = (Uint32) SDL_ReadLE16(src);
   118         biHeight = (Uint32) SDL_ReadLE16(src);
   119         /*biPlanes =*/ SDL_ReadLE16(src);
   120         biBitCount = SDL_ReadLE16(src);
   121         biCompression = BI_RGB;
   122     } else {
   123         biWidth = SDL_ReadLE32(src);
   124         biHeight = SDL_ReadLE32(src);
   125         /*biPlanes =*/ SDL_ReadLE16(src);
   126         biBitCount = SDL_ReadLE16(src);
   127         biCompression = SDL_ReadLE32(src);
   128         /*biSizeImage =*/ SDL_ReadLE32(src);
   129         /*biXPelsPerMeter =*/ SDL_ReadLE32(src);
   130         /*biYPelsPerMeter =*/ SDL_ReadLE32(src);
   131         biClrUsed = SDL_ReadLE32(src);
   132         /*biClrImportant =*/ SDL_ReadLE32(src);
   133     }
   134     if (biHeight < 0) {
   135         topDown = SDL_TRUE;
   136         biHeight = -biHeight;
   137     } else {
   138         topDown = SDL_FALSE;
   139     }
   140 
   141     /* Check for read error */
   142     if (SDL_strcmp(SDL_GetError(), "") != 0) {
   143         was_error = SDL_TRUE;
   144         goto done;
   145     }
   146 
   147     /* Expand 1 and 4 bit bitmaps to 8 bits per pixel */
   148     switch (biBitCount) {
   149     case 1:
   150     case 4:
   151         ExpandBMP = biBitCount;
   152         biBitCount = 8;
   153         break;
   154     default:
   155         ExpandBMP = 0;
   156         break;
   157     }
   158 
   159     /* We don't support any BMP compression right now */
   160     Rmask = Gmask = Bmask = Amask = 0;
   161     switch (biCompression) {
   162     case BI_RGB:
   163         /* If there are no masks, use the defaults */
   164         if (bfOffBits == (14 + biSize)) {
   165             /* Default values for the BMP format */
   166             switch (biBitCount) {
   167             case 15:
   168             case 16:
   169                 Rmask = 0x7C00;
   170                 Gmask = 0x03E0;
   171                 Bmask = 0x001F;
   172                 break;
   173             case 24:
   174 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
   175                 Rmask = 0x000000FF;
   176                 Gmask = 0x0000FF00;
   177                 Bmask = 0x00FF0000;
   178 #else
   179                 Rmask = 0x00FF0000;
   180                 Gmask = 0x0000FF00;
   181                 Bmask = 0x000000FF;
   182 #endif
   183                 break;
   184             case 32:
   185                 Amask = 0xFF000000;
   186                 Rmask = 0x00FF0000;
   187                 Gmask = 0x0000FF00;
   188                 Bmask = 0x000000FF;
   189                 break;
   190             default:
   191                 break;
   192             }
   193             break;
   194         }
   195         /* Fall through -- read the RGB masks */
   196 
   197     case BI_BITFIELDS:
   198         switch (biBitCount) {
   199         case 15:
   200         case 16:
   201             Rmask = SDL_ReadLE32(src);
   202             Gmask = SDL_ReadLE32(src);
   203             Bmask = SDL_ReadLE32(src);
   204             break;
   205         case 32:
   206             Rmask = SDL_ReadLE32(src);
   207             Gmask = SDL_ReadLE32(src);
   208             Bmask = SDL_ReadLE32(src);
   209             Amask = SDL_ReadLE32(src);
   210             break;
   211         default:
   212             break;
   213         }
   214         break;
   215     default:
   216         SDL_SetError("Compressed BMP files not supported");
   217         was_error = SDL_TRUE;
   218         goto done;
   219     }
   220 
   221     /* Create a compatible surface, note that the colors are RGB ordered */
   222     surface =
   223         SDL_CreateRGBSurface(0, biWidth, biHeight, biBitCount, Rmask, Gmask,
   224                              Bmask, Amask);
   225     if (surface == NULL) {
   226         was_error = SDL_TRUE;
   227         goto done;
   228     }
   229 
   230     /* Load the palette, if any */
   231     palette = (surface->format)->palette;
   232     if (palette) {
   233         if (biClrUsed == 0) {
   234             biClrUsed = 1 << biBitCount;
   235         }
   236         if ((int) biClrUsed > palette->ncolors) {
   237             palette->ncolors = biClrUsed;
   238             palette->colors =
   239                 (SDL_Color *) SDL_realloc(palette->colors,
   240                                           palette->ncolors *
   241                                           sizeof(*palette->colors));
   242             if (!palette->colors) {
   243                 SDL_OutOfMemory();
   244                 was_error = SDL_TRUE;
   245                 goto done;
   246             }
   247         } else if ((int) biClrUsed < palette->ncolors) {
   248             palette->ncolors = biClrUsed;
   249         }
   250         if (biSize == 12) {
   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                 palette->colors[i].a = SDL_ALPHA_OPAQUE;
   256             }
   257         } else {
   258             for (i = 0; i < (int) biClrUsed; ++i) {
   259                 SDL_RWread(src, &palette->colors[i].b, 1, 1);
   260                 SDL_RWread(src, &palette->colors[i].g, 1, 1);
   261                 SDL_RWread(src, &palette->colors[i].r, 1, 1);
   262                 SDL_RWread(src, &palette->colors[i].a, 1, 1);
   263 
   264                 /* According to Microsoft documentation, the fourth element
   265                    is reserved and must be zero, so we shouldn't treat it as
   266                    alpha.
   267                 */
   268                 palette->colors[i].a = SDL_ALPHA_OPAQUE;
   269             }
   270         }
   271     }
   272 
   273     /* Read the surface pixels.  Note that the bmp image is upside down */
   274     if (SDL_RWseek(src, fp_offset + bfOffBits, RW_SEEK_SET) < 0) {
   275         SDL_Error(SDL_EFSEEK);
   276         was_error = SDL_TRUE;
   277         goto done;
   278     }
   279     top = (Uint8 *)surface->pixels;
   280     end = (Uint8 *)surface->pixels+(surface->h*surface->pitch);
   281     switch (ExpandBMP) {
   282     case 1:
   283         bmpPitch = (biWidth + 7) >> 3;
   284         pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0);
   285         break;
   286     case 4:
   287         bmpPitch = (biWidth + 1) >> 1;
   288         pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0);
   289         break;
   290     default:
   291         pad = ((surface->pitch % 4) ? (4 - (surface->pitch % 4)) : 0);
   292         break;
   293     }
   294     if (topDown) {
   295         bits = top;
   296     } else {
   297         bits = end - surface->pitch;
   298     }
   299     while (bits >= top && bits < end) {
   300         switch (ExpandBMP) {
   301         case 1:
   302         case 4:{
   303                 Uint8 pixel = 0;
   304                 int shift = (8 - ExpandBMP);
   305                 for (i = 0; i < surface->w; ++i) {
   306                     if (i % (8 / ExpandBMP) == 0) {
   307                         if (!SDL_RWread(src, &pixel, 1, 1)) {
   308                             SDL_SetError("Error reading from BMP");
   309                             was_error = SDL_TRUE;
   310                             goto done;
   311                         }
   312                     }
   313                     *(bits + i) = (pixel >> shift);
   314                     pixel <<= ExpandBMP;
   315                 }
   316             }
   317             break;
   318 
   319         default:
   320             if (SDL_RWread(src, bits, 1, surface->pitch)
   321                 != surface->pitch) {
   322                 SDL_Error(SDL_EFREAD);
   323                 was_error = SDL_TRUE;
   324                 goto done;
   325             }
   326 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
   327             /* Byte-swap the pixels if needed. Note that the 24bpp
   328                case has already been taken care of above. */
   329             switch (biBitCount) {
   330             case 15:
   331             case 16:{
   332                     Uint16 *pix = (Uint16 *) bits;
   333                     for (i = 0; i < surface->w; i++)
   334                         pix[i] = SDL_Swap16(pix[i]);
   335                     break;
   336                 }
   337 
   338             case 32:{
   339                     Uint32 *pix = (Uint32 *) bits;
   340                     for (i = 0; i < surface->w; i++)
   341                         pix[i] = SDL_Swap32(pix[i]);
   342                     break;
   343                 }
   344             }
   345 #endif
   346             break;
   347         }
   348         /* Skip padding bytes, ugh */
   349         if (pad) {
   350             Uint8 padbyte;
   351             for (i = 0; i < pad; ++i) {
   352                 SDL_RWread(src, &padbyte, 1, 1);
   353             }
   354         }
   355         if (topDown) {
   356             bits += surface->pitch;
   357         } else {
   358             bits -= surface->pitch;
   359         }
   360     }
   361   done:
   362     if (was_error) {
   363         if (src) {
   364             SDL_RWseek(src, fp_offset, RW_SEEK_SET);
   365         }
   366         if (surface) {
   367             SDL_FreeSurface(surface);
   368         }
   369         surface = NULL;
   370     }
   371     if (freesrc && src) {
   372         SDL_RWclose(src);
   373     }
   374     return (surface);
   375 }
   376 
   377 int
   378 SDL_SaveBMP_RW(SDL_Surface * saveme, SDL_RWops * dst, int freedst)
   379 {
   380     Sint64 fp_offset;
   381     int i, pad;
   382     SDL_Surface *surface;
   383     Uint8 *bits;
   384 
   385     /* The Win32 BMP file header (14 bytes) */
   386     char magic[2] = { 'B', 'M' };
   387     Uint32 bfSize;
   388     Uint16 bfReserved1;
   389     Uint16 bfReserved2;
   390     Uint32 bfOffBits;
   391 
   392     /* The Win32 BITMAPINFOHEADER struct (40 bytes) */
   393     Uint32 biSize;
   394     Sint32 biWidth;
   395     Sint32 biHeight;
   396     Uint16 biPlanes;
   397     Uint16 biBitCount;
   398     Uint32 biCompression;
   399     Uint32 biSizeImage;
   400     Sint32 biXPelsPerMeter;
   401     Sint32 biYPelsPerMeter;
   402     Uint32 biClrUsed;
   403     Uint32 biClrImportant;
   404 
   405     /* Make sure we have somewhere to save */
   406     surface = NULL;
   407     if (dst) {
   408         SDL_bool save32bit = SDL_FALSE;
   409 #ifdef SAVE_32BIT_BMP
   410         /* We can save alpha information in a 32-bit BMP */
   411         if (saveme->map->info.flags & SDL_COPY_COLORKEY ||
   412             saveme->format->Amask) {
   413             save32bit = SDL_TRUE;
   414         }
   415 #endif /* SAVE_32BIT_BMP */
   416 
   417         if (saveme->format->palette && !save32bit) {
   418             if (saveme->format->BitsPerPixel == 8) {
   419                 surface = saveme;
   420             } else {
   421                 SDL_SetError("%d bpp BMP files not supported",
   422                              saveme->format->BitsPerPixel);
   423             }
   424         } else if ((saveme->format->BitsPerPixel == 24) &&
   425 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
   426                    (saveme->format->Rmask == 0x00FF0000) &&
   427                    (saveme->format->Gmask == 0x0000FF00) &&
   428                    (saveme->format->Bmask == 0x000000FF)
   429 #else
   430                    (saveme->format->Rmask == 0x000000FF) &&
   431                    (saveme->format->Gmask == 0x0000FF00) &&
   432                    (saveme->format->Bmask == 0x00FF0000)
   433 #endif
   434             ) {
   435             surface = saveme;
   436         } else {
   437             SDL_PixelFormat format;
   438 
   439             /* If the surface has a colorkey or alpha channel we'll save a
   440                32-bit BMP with alpha channel, otherwise save a 24-bit BMP. */
   441             if (save32bit) {
   442                 SDL_InitFormat(&format,
   443 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
   444                                SDL_PIXELFORMAT_ARGB8888
   445 #else
   446                                SDL_PIXELFORMAT_BGRA8888
   447 #endif
   448                                );
   449             } else {
   450                 SDL_InitFormat(&format, SDL_PIXELFORMAT_BGR24);
   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].a, 1, 1);
   520             }
   521         }
   522 
   523         /* Write the bitmap offset */
   524         bfOffBits = (Uint32)(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 = (Uint32)(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: */