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