src/video/SDL_bmp.c
author Ryan C. Gordon <icculus@icculus.org>
Sun, 24 Nov 2013 23:56:17 -0500
changeset 8093 b43765095a6f
parent 7921 74dd62231c5a
child 8149 681eb46b8ac4
permissions -rw-r--r--
Make internal SDL sources include SDL_internal.h instead of SDL_config.h

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