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