src/video/SDL_bmp.c
author Gabriel Jacobo <gabomdq@gmail.com>
Wed, 21 Aug 2013 09:47:10 -0300
changeset 7678 286c42d7c5ed
parent 7677 871d43c6968a
child 7720 f9a649383362
permissions -rw-r--r--
OCD fixes: Adds a space after /* (glory to regular expressions!)
     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         if (surface) {
   402             SDL_FreeSurface(surface);
   403         }
   404         surface = NULL;
   405     }
   406     if (freesrc && src) {
   407         SDL_RWclose(src);
   408     }
   409     return (surface);
   410 }
   411 
   412 int
   413 SDL_SaveBMP_RW(SDL_Surface * saveme, SDL_RWops * dst, int freedst)
   414 {
   415     Sint64 fp_offset;
   416     int i, pad;
   417     SDL_Surface *surface;
   418     Uint8 *bits;
   419 
   420     /* The Win32 BMP file header (14 bytes) */
   421     char magic[2] = { 'B', 'M' };
   422     Uint32 bfSize;
   423     Uint16 bfReserved1;
   424     Uint16 bfReserved2;
   425     Uint32 bfOffBits;
   426 
   427     /* The Win32 BITMAPINFOHEADER struct (40 bytes) */
   428     Uint32 biSize;
   429     Sint32 biWidth;
   430     Sint32 biHeight;
   431     Uint16 biPlanes;
   432     Uint16 biBitCount;
   433     Uint32 biCompression;
   434     Uint32 biSizeImage;
   435     Sint32 biXPelsPerMeter;
   436     Sint32 biYPelsPerMeter;
   437     Uint32 biClrUsed;
   438     Uint32 biClrImportant;
   439 
   440     /* Make sure we have somewhere to save */
   441     surface = NULL;
   442     if (dst) {
   443         SDL_bool save32bit = SDL_FALSE;
   444 #ifdef SAVE_32BIT_BMP
   445         /* We can save alpha information in a 32-bit BMP */
   446         if (saveme->map->info.flags & SDL_COPY_COLORKEY ||
   447             saveme->format->Amask) {
   448             save32bit = SDL_TRUE;
   449         }
   450 #endif /* SAVE_32BIT_BMP */
   451 
   452         if (saveme->format->palette && !save32bit) {
   453             if (saveme->format->BitsPerPixel == 8) {
   454                 surface = saveme;
   455             } else {
   456                 SDL_SetError("%d bpp BMP files not supported",
   457                              saveme->format->BitsPerPixel);
   458             }
   459         } else if ((saveme->format->BitsPerPixel == 24) &&
   460 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
   461                    (saveme->format->Rmask == 0x00FF0000) &&
   462                    (saveme->format->Gmask == 0x0000FF00) &&
   463                    (saveme->format->Bmask == 0x000000FF)
   464 #else
   465                    (saveme->format->Rmask == 0x000000FF) &&
   466                    (saveme->format->Gmask == 0x0000FF00) &&
   467                    (saveme->format->Bmask == 0x00FF0000)
   468 #endif
   469             ) {
   470             surface = saveme;
   471         } else {
   472             SDL_PixelFormat format;
   473 
   474             /* If the surface has a colorkey or alpha channel we'll save a
   475                32-bit BMP with alpha channel, otherwise save a 24-bit BMP. */
   476             if (save32bit) {
   477                 SDL_InitFormat(&format,
   478 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
   479                                SDL_PIXELFORMAT_ARGB8888
   480 #else
   481                                SDL_PIXELFORMAT_BGRA8888
   482 #endif
   483                                );
   484             } else {
   485                 SDL_InitFormat(&format, SDL_PIXELFORMAT_BGR24);
   486             }
   487             surface = SDL_ConvertSurface(saveme, &format, 0);
   488             if (!surface) {
   489                 SDL_SetError("Couldn't convert image to %d bpp",
   490                              format.BitsPerPixel);
   491             }
   492         }
   493     }
   494 
   495     if (surface && (SDL_LockSurface(surface) == 0)) {
   496         const int bw = surface->w * surface->format->BytesPerPixel;
   497 
   498         /* Set the BMP file header values */
   499         bfSize = 0;             /* We'll write this when we're done */
   500         bfReserved1 = 0;
   501         bfReserved2 = 0;
   502         bfOffBits = 0;          /* We'll write this when we're done */
   503 
   504         /* Write the BMP file header values */
   505         fp_offset = SDL_RWtell(dst);
   506         SDL_ClearError();
   507         SDL_RWwrite(dst, magic, 2, 1);
   508         SDL_WriteLE32(dst, bfSize);
   509         SDL_WriteLE16(dst, bfReserved1);
   510         SDL_WriteLE16(dst, bfReserved2);
   511         SDL_WriteLE32(dst, bfOffBits);
   512 
   513         /* Set the BMP info values */
   514         biSize = 40;
   515         biWidth = surface->w;
   516         biHeight = surface->h;
   517         biPlanes = 1;
   518         biBitCount = surface->format->BitsPerPixel;
   519         biCompression = BI_RGB;
   520         biSizeImage = surface->h * surface->pitch;
   521         biXPelsPerMeter = 0;
   522         biYPelsPerMeter = 0;
   523         if (surface->format->palette) {
   524             biClrUsed = surface->format->palette->ncolors;
   525         } else {
   526             biClrUsed = 0;
   527         }
   528         biClrImportant = 0;
   529 
   530         /* Write the BMP info values */
   531         SDL_WriteLE32(dst, biSize);
   532         SDL_WriteLE32(dst, biWidth);
   533         SDL_WriteLE32(dst, biHeight);
   534         SDL_WriteLE16(dst, biPlanes);
   535         SDL_WriteLE16(dst, biBitCount);
   536         SDL_WriteLE32(dst, biCompression);
   537         SDL_WriteLE32(dst, biSizeImage);
   538         SDL_WriteLE32(dst, biXPelsPerMeter);
   539         SDL_WriteLE32(dst, biYPelsPerMeter);
   540         SDL_WriteLE32(dst, biClrUsed);
   541         SDL_WriteLE32(dst, biClrImportant);
   542 
   543         /* Write the palette (in BGR color order) */
   544         if (surface->format->palette) {
   545             SDL_Color *colors;
   546             int ncolors;
   547 
   548             colors = surface->format->palette->colors;
   549             ncolors = surface->format->palette->ncolors;
   550             for (i = 0; i < ncolors; ++i) {
   551                 SDL_RWwrite(dst, &colors[i].b, 1, 1);
   552                 SDL_RWwrite(dst, &colors[i].g, 1, 1);
   553                 SDL_RWwrite(dst, &colors[i].r, 1, 1);
   554                 SDL_RWwrite(dst, &colors[i].a, 1, 1);
   555             }
   556         }
   557 
   558         /* Write the bitmap offset */
   559         bfOffBits = (Uint32)(SDL_RWtell(dst) - fp_offset);
   560         if (SDL_RWseek(dst, fp_offset + 10, RW_SEEK_SET) < 0) {
   561             SDL_Error(SDL_EFSEEK);
   562         }
   563         SDL_WriteLE32(dst, bfOffBits);
   564         if (SDL_RWseek(dst, fp_offset + bfOffBits, RW_SEEK_SET) < 0) {
   565             SDL_Error(SDL_EFSEEK);
   566         }
   567 
   568         /* Write the bitmap image upside down */
   569         bits = (Uint8 *) surface->pixels + (surface->h * surface->pitch);
   570         pad = ((bw % 4) ? (4 - (bw % 4)) : 0);
   571         while (bits > (Uint8 *) surface->pixels) {
   572             bits -= surface->pitch;
   573             if (SDL_RWwrite(dst, bits, 1, bw) != bw) {
   574                 SDL_Error(SDL_EFWRITE);
   575                 break;
   576             }
   577             if (pad) {
   578                 const Uint8 padbyte = 0;
   579                 for (i = 0; i < pad; ++i) {
   580                     SDL_RWwrite(dst, &padbyte, 1, 1);
   581                 }
   582             }
   583         }
   584 
   585         /* Write the BMP file size */
   586         bfSize = (Uint32)(SDL_RWtell(dst) - fp_offset);
   587         if (SDL_RWseek(dst, fp_offset + 2, RW_SEEK_SET) < 0) {
   588             SDL_Error(SDL_EFSEEK);
   589         }
   590         SDL_WriteLE32(dst, bfSize);
   591         if (SDL_RWseek(dst, fp_offset + bfSize, RW_SEEK_SET) < 0) {
   592             SDL_Error(SDL_EFSEEK);
   593         }
   594 
   595         /* Close it up.. */
   596         SDL_UnlockSurface(surface);
   597         if (surface != saveme) {
   598             SDL_FreeSurface(surface);
   599         }
   600     }
   601 
   602     if (freedst && dst) {
   603         SDL_RWclose(dst);
   604     }
   605     return ((SDL_strcmp(SDL_GetError(), "") == 0) ? 0 : -1);
   606 }
   607 
   608 /* vi: set ts=4 sw=4 expandtab: */