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