src/video/SDL_bmp.c
author Sam Lantinga <slouken@libsdl.org>
Mon, 29 May 2006 04:04:35 +0000
branchSDL-1.3
changeset 1668 4da1ee79c9af
parent 1662 782fd950bd46
child 1670 eef792d31de8
permissions -rw-r--r--
more tweaking indent options
     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 = SDL_CreateRGBSurface(SDL_SWSURFACE,
   207                                    biWidth, biHeight, biBitCount, Rmask,
   208                                    Gmask, 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 = 0;
   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         palette->ncolors = biClrUsed;
   236     }
   237 
   238     /* Read the surface pixels.  Note that the bmp image is upside down */
   239     if (SDL_RWseek(src, fp_offset + bfOffBits, RW_SEEK_SET) < 0) {
   240         SDL_Error(SDL_EFSEEK);
   241         was_error = 1;
   242         goto done;
   243     }
   244     bits = (Uint8 *) surface->pixels + (surface->h * surface->pitch);
   245     switch (ExpandBMP) {
   246     case 1:
   247         bmpPitch = (biWidth + 7) >> 3;
   248         pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0);
   249         break;
   250     case 4:
   251         bmpPitch = (biWidth + 1) >> 1;
   252         pad = (((bmpPitch) % 4) ? (4 - ((bmpPitch) % 4)) : 0);
   253         break;
   254     default:
   255         pad = ((surface->pitch % 4) ? (4 - (surface->pitch % 4)) : 0);
   256         break;
   257     }
   258     while (bits > (Uint8 *) surface->pixels) {
   259         bits -= surface->pitch;
   260         switch (ExpandBMP) {
   261         case 1:
   262         case 4:
   263             {
   264                 Uint8 pixel = 0;
   265                 int shift = (8 - ExpandBMP);
   266                 for (i = 0; i < surface->w; ++i) {
   267                     if (i % (8 / ExpandBMP) == 0) {
   268                         if (!SDL_RWread(src, &pixel, 1, 1)) {
   269                             SDL_SetError("Error reading from BMP");
   270                             was_error = 1;
   271                             goto done;
   272                         }
   273                     }
   274                     *(bits + i) = (pixel >> shift);
   275                     pixel <<= ExpandBMP;
   276                 }
   277             }
   278             break;
   279 
   280         default:
   281             if (SDL_RWread(src, bits, 1, surface->pitch)
   282                 != surface->pitch) {
   283                 SDL_Error(SDL_EFREAD);
   284                 was_error = 1;
   285                 goto done;
   286             }
   287 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
   288             /* Byte-swap the pixels if needed. Note that the 24bpp
   289                case has already been taken care of above. */
   290             switch (biBitCount) {
   291             case 15:
   292             case 16:
   293                 {
   294                     Uint16 *pix = (Uint16 *) bits;
   295                     for (i = 0; i < surface->w; i++)
   296                         pix[i] = SDL_Swap16(pix[i]);
   297                     break;
   298                 }
   299 
   300             case 32:
   301                 {
   302                     Uint32 *pix = (Uint32 *) bits;
   303                     for (i = 0; i < surface->w; i++)
   304                         pix[i] = SDL_Swap32(pix[i]);
   305                     break;
   306                 }
   307             }
   308 #endif
   309             break;
   310         }
   311         /* Skip padding bytes, ugh */
   312         if (pad) {
   313             Uint8 padbyte;
   314             for (i = 0; i < pad; ++i) {
   315                 SDL_RWread(src, &padbyte, 1, 1);
   316             }
   317         }
   318     }
   319   done:
   320     if (was_error) {
   321         if (src) {
   322             SDL_RWseek(src, fp_offset, RW_SEEK_SET);
   323         }
   324         if (surface) {
   325             SDL_FreeSurface(surface);
   326         }
   327         surface = NULL;
   328     }
   329     if (freesrc && src) {
   330         SDL_RWclose(src);
   331     }
   332     return (surface);
   333 }
   334 
   335 int
   336 SDL_SaveBMP_RW(SDL_Surface * saveme, SDL_RWops * dst, int freedst)
   337 {
   338     long fp_offset;
   339     int i, pad;
   340     SDL_Surface *surface;
   341     Uint8 *bits;
   342 
   343     /* The Win32 BMP file header (14 bytes) */
   344     char magic[2] = { 'B', 'M' };
   345     Uint32 bfSize;
   346     Uint16 bfReserved1;
   347     Uint16 bfReserved2;
   348     Uint32 bfOffBits;
   349 
   350     /* The Win32 BITMAPINFOHEADER struct (40 bytes) */
   351     Uint32 biSize;
   352     Sint32 biWidth;
   353     Sint32 biHeight;
   354     Uint16 biPlanes;
   355     Uint16 biBitCount;
   356     Uint32 biCompression;
   357     Uint32 biSizeImage;
   358     Sint32 biXPelsPerMeter;
   359     Sint32 biYPelsPerMeter;
   360     Uint32 biClrUsed;
   361     Uint32 biClrImportant;
   362 
   363     /* Make sure we have somewhere to save */
   364     surface = NULL;
   365     if (dst) {
   366         if (saveme->format->palette) {
   367             if (saveme->format->BitsPerPixel == 8) {
   368                 surface = saveme;
   369             } else {
   370                 SDL_SetError("%d bpp BMP files not supported",
   371                              saveme->format->BitsPerPixel);
   372             }
   373         } else if ((saveme->format->BitsPerPixel == 24) &&
   374 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
   375                    (saveme->format->Rmask == 0x00FF0000) &&
   376                    (saveme->format->Gmask == 0x0000FF00) &&
   377                    (saveme->format->Bmask == 0x000000FF)
   378 #else
   379                    (saveme->format->Rmask == 0x000000FF) &&
   380                    (saveme->format->Gmask == 0x0000FF00) &&
   381                    (saveme->format->Bmask == 0x00FF0000)
   382 #endif
   383             ) {
   384             surface = saveme;
   385         } else {
   386             SDL_Rect bounds;
   387 
   388             /* Convert to 24 bits per pixel */
   389             surface = SDL_CreateRGBSurface(SDL_SWSURFACE,
   390                                            saveme->w, saveme->h, 24,
   391 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
   392                                            0x00FF0000, 0x0000FF00, 0x000000FF,
   393 #else
   394                                            0x000000FF, 0x0000FF00, 0x00FF0000,
   395 #endif
   396                                            0);
   397             if (surface != NULL) {
   398                 bounds.x = 0;
   399                 bounds.y = 0;
   400                 bounds.w = saveme->w;
   401                 bounds.h = saveme->h;
   402                 if (SDL_LowerBlit(saveme, &bounds, surface, &bounds) < 0) {
   403                     SDL_FreeSurface(surface);
   404                     SDL_SetError("Couldn't convert image to 24 bpp");
   405                     surface = NULL;
   406                 }
   407             }
   408         }
   409     }
   410 
   411     if (surface && (SDL_LockSurface(surface) == 0)) {
   412         const int bw = surface->w * surface->format->BytesPerPixel;
   413 
   414         /* Set the BMP file header values */
   415         bfSize = 0;             /* We'll write this when we're done */
   416         bfReserved1 = 0;
   417         bfReserved2 = 0;
   418         bfOffBits = 0;          /* We'll write this when we're done */
   419 
   420         /* Write the BMP file header values */
   421         fp_offset = SDL_RWtell(dst);
   422         SDL_ClearError();
   423         SDL_RWwrite(dst, magic, 2, 1);
   424         SDL_WriteLE32(dst, bfSize);
   425         SDL_WriteLE16(dst, bfReserved1);
   426         SDL_WriteLE16(dst, bfReserved2);
   427         SDL_WriteLE32(dst, bfOffBits);
   428 
   429         /* Set the BMP info values */
   430         biSize = 40;
   431         biWidth = surface->w;
   432         biHeight = surface->h;
   433         biPlanes = 1;
   434         biBitCount = surface->format->BitsPerPixel;
   435         biCompression = BI_RGB;
   436         biSizeImage = surface->h * surface->pitch;
   437         biXPelsPerMeter = 0;
   438         biYPelsPerMeter = 0;
   439         if (surface->format->palette) {
   440             biClrUsed = surface->format->palette->ncolors;
   441         } else {
   442             biClrUsed = 0;
   443         }
   444         biClrImportant = 0;
   445 
   446         /* Write the BMP info values */
   447         SDL_WriteLE32(dst, biSize);
   448         SDL_WriteLE32(dst, biWidth);
   449         SDL_WriteLE32(dst, biHeight);
   450         SDL_WriteLE16(dst, biPlanes);
   451         SDL_WriteLE16(dst, biBitCount);
   452         SDL_WriteLE32(dst, biCompression);
   453         SDL_WriteLE32(dst, biSizeImage);
   454         SDL_WriteLE32(dst, biXPelsPerMeter);
   455         SDL_WriteLE32(dst, biYPelsPerMeter);
   456         SDL_WriteLE32(dst, biClrUsed);
   457         SDL_WriteLE32(dst, biClrImportant);
   458 
   459         /* Write the palette (in BGR color order) */
   460         if (surface->format->palette) {
   461             SDL_Color *colors;
   462             int ncolors;
   463 
   464             colors = surface->format->palette->colors;
   465             ncolors = surface->format->palette->ncolors;
   466             for (i = 0; i < ncolors; ++i) {
   467                 SDL_RWwrite(dst, &colors[i].b, 1, 1);
   468                 SDL_RWwrite(dst, &colors[i].g, 1, 1);
   469                 SDL_RWwrite(dst, &colors[i].r, 1, 1);
   470                 SDL_RWwrite(dst, &colors[i].unused, 1, 1);
   471             }
   472         }
   473 
   474         /* Write the bitmap offset */
   475         bfOffBits = SDL_RWtell(dst) - fp_offset;
   476         if (SDL_RWseek(dst, fp_offset + 10, RW_SEEK_SET) < 0) {
   477             SDL_Error(SDL_EFSEEK);
   478         }
   479         SDL_WriteLE32(dst, bfOffBits);
   480         if (SDL_RWseek(dst, fp_offset + bfOffBits, RW_SEEK_SET) < 0) {
   481             SDL_Error(SDL_EFSEEK);
   482         }
   483 
   484         /* Write the bitmap image upside down */
   485         bits = (Uint8 *) surface->pixels + (surface->h * surface->pitch);
   486         pad = ((bw % 4) ? (4 - (bw % 4)) : 0);
   487         while (bits > (Uint8 *) surface->pixels) {
   488             bits -= surface->pitch;
   489             if (SDL_RWwrite(dst, bits, 1, bw) != bw) {
   490                 SDL_Error(SDL_EFWRITE);
   491                 break;
   492             }
   493             if (pad) {
   494                 const Uint8 padbyte = 0;
   495                 for (i = 0; i < pad; ++i) {
   496                     SDL_RWwrite(dst, &padbyte, 1, 1);
   497                 }
   498             }
   499         }
   500 
   501         /* Write the BMP file size */
   502         bfSize = SDL_RWtell(dst) - fp_offset;
   503         if (SDL_RWseek(dst, fp_offset + 2, RW_SEEK_SET) < 0) {
   504             SDL_Error(SDL_EFSEEK);
   505         }
   506         SDL_WriteLE32(dst, bfSize);
   507         if (SDL_RWseek(dst, fp_offset + bfSize, RW_SEEK_SET) < 0) {
   508             SDL_Error(SDL_EFSEEK);
   509         }
   510 
   511         /* Close it up.. */
   512         SDL_UnlockSurface(surface);
   513         if (surface != saveme) {
   514             SDL_FreeSurface(surface);
   515         }
   516     }
   517 
   518     if (freedst && dst) {
   519         SDL_RWclose(dst);
   520     }
   521     return ((SDL_strcmp(SDL_GetError(), "") == 0) ? 0 : -1);
   522 }
   523 
   524 /* vi: set ts=4 sw=4 expandtab: */