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