src/video/SDL_bmp.c
author Sam Lantinga <slouken@libsdl.org>
Mon, 10 Jul 2006 21:04:37 +0000
changeset 1895 c121d94672cb
parent 1402 d910939febfa
child 2807 365fe1a2aad5
permissions -rw-r--r--
SDL 1.2 is moving to a branch, and SDL 1.3 is becoming the head.
     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_Rect bounds;
   386 
   387             /* Convert to 24 bits per pixel */
   388             surface = SDL_CreateRGBSurface(0, saveme->w, saveme->h, 24,
   389 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
   390                                            0x00FF0000, 0x0000FF00, 0x000000FF,
   391 #else
   392                                            0x000000FF, 0x0000FF00, 0x00FF0000,
   393 #endif
   394                                            0);
   395             if (surface != NULL) {
   396                 bounds.x = 0;
   397                 bounds.y = 0;
   398                 bounds.w = saveme->w;
   399                 bounds.h = saveme->h;
   400                 if (SDL_LowerBlit(saveme, &bounds, surface, &bounds) < 0) {
   401                     SDL_FreeSurface(surface);
   402                     SDL_SetError("Couldn't convert image to 24 bpp");
   403                     surface = NULL;
   404                 }
   405             }
   406         }
   407     }
   408 
   409     if (surface && (SDL_LockSurface(surface) == 0)) {
   410         const int bw = surface->w * surface->format->BytesPerPixel;
   411 
   412         /* Set the BMP file header values */
   413         bfSize = 0;             /* We'll write this when we're done */
   414         bfReserved1 = 0;
   415         bfReserved2 = 0;
   416         bfOffBits = 0;          /* We'll write this when we're done */
   417 
   418         /* Write the BMP file header values */
   419         fp_offset = SDL_RWtell(dst);
   420         SDL_ClearError();
   421         SDL_RWwrite(dst, magic, 2, 1);
   422         SDL_WriteLE32(dst, bfSize);
   423         SDL_WriteLE16(dst, bfReserved1);
   424         SDL_WriteLE16(dst, bfReserved2);
   425         SDL_WriteLE32(dst, bfOffBits);
   426 
   427         /* Set the BMP info values */
   428         biSize = 40;
   429         biWidth = surface->w;
   430         biHeight = surface->h;
   431         biPlanes = 1;
   432         biBitCount = surface->format->BitsPerPixel;
   433         biCompression = BI_RGB;
   434         biSizeImage = surface->h * surface->pitch;
   435         biXPelsPerMeter = 0;
   436         biYPelsPerMeter = 0;
   437         if (surface->format->palette) {
   438             biClrUsed = surface->format->palette->ncolors;
   439         } else {
   440             biClrUsed = 0;
   441         }
   442         biClrImportant = 0;
   443 
   444         /* Write the BMP info values */
   445         SDL_WriteLE32(dst, biSize);
   446         SDL_WriteLE32(dst, biWidth);
   447         SDL_WriteLE32(dst, biHeight);
   448         SDL_WriteLE16(dst, biPlanes);
   449         SDL_WriteLE16(dst, biBitCount);
   450         SDL_WriteLE32(dst, biCompression);
   451         SDL_WriteLE32(dst, biSizeImage);
   452         SDL_WriteLE32(dst, biXPelsPerMeter);
   453         SDL_WriteLE32(dst, biYPelsPerMeter);
   454         SDL_WriteLE32(dst, biClrUsed);
   455         SDL_WriteLE32(dst, biClrImportant);
   456 
   457         /* Write the palette (in BGR color order) */
   458         if (surface->format->palette) {
   459             SDL_Color *colors;
   460             int ncolors;
   461 
   462             colors = surface->format->palette->colors;
   463             ncolors = surface->format->palette->ncolors;
   464             for (i = 0; i < ncolors; ++i) {
   465                 SDL_RWwrite(dst, &colors[i].b, 1, 1);
   466                 SDL_RWwrite(dst, &colors[i].g, 1, 1);
   467                 SDL_RWwrite(dst, &colors[i].r, 1, 1);
   468                 SDL_RWwrite(dst, &colors[i].unused, 1, 1);
   469             }
   470         }
   471 
   472         /* Write the bitmap offset */
   473         bfOffBits = SDL_RWtell(dst) - fp_offset;
   474         if (SDL_RWseek(dst, fp_offset + 10, RW_SEEK_SET) < 0) {
   475             SDL_Error(SDL_EFSEEK);
   476         }
   477         SDL_WriteLE32(dst, bfOffBits);
   478         if (SDL_RWseek(dst, fp_offset + bfOffBits, RW_SEEK_SET) < 0) {
   479             SDL_Error(SDL_EFSEEK);
   480         }
   481 
   482         /* Write the bitmap image upside down */
   483         bits = (Uint8 *) surface->pixels + (surface->h * surface->pitch);
   484         pad = ((bw % 4) ? (4 - (bw % 4)) : 0);
   485         while (bits > (Uint8 *) surface->pixels) {
   486             bits -= surface->pitch;
   487             if (SDL_RWwrite(dst, bits, 1, bw) != bw) {
   488                 SDL_Error(SDL_EFWRITE);
   489                 break;
   490             }
   491             if (pad) {
   492                 const Uint8 padbyte = 0;
   493                 for (i = 0; i < pad; ++i) {
   494                     SDL_RWwrite(dst, &padbyte, 1, 1);
   495                 }
   496             }
   497         }
   498 
   499         /* Write the BMP file size */
   500         bfSize = SDL_RWtell(dst) - fp_offset;
   501         if (SDL_RWseek(dst, fp_offset + 2, RW_SEEK_SET) < 0) {
   502             SDL_Error(SDL_EFSEEK);
   503         }
   504         SDL_WriteLE32(dst, bfSize);
   505         if (SDL_RWseek(dst, fp_offset + bfSize, RW_SEEK_SET) < 0) {
   506             SDL_Error(SDL_EFSEEK);
   507         }
   508 
   509         /* Close it up.. */
   510         SDL_UnlockSurface(surface);
   511         if (surface != saveme) {
   512             SDL_FreeSurface(surface);
   513         }
   514     }
   515 
   516     if (freedst && dst) {
   517         SDL_RWclose(dst);
   518     }
   519     return ((SDL_strcmp(SDL_GetError(), "") == 0) ? 0 : -1);
   520 }
   521 
   522 /* vi: set ts=4 sw=4 expandtab: */