src/video/SDL_bmp.c
author Sam Lantinga <slouken@libsdl.org>
Fri, 10 Feb 2006 06:48:43 +0000
changeset 1358 c71e05b4dc2e
parent 1336 3692456e7b0f
child 1361 19418e4422cb
permissions -rw-r--r--
More header massaging... works great on Windows. ;-)
     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 
    23 #ifndef DISABLE_FILE
    24 
    25 /* 
    26    Code to load and save surfaces in Windows BMP format.
    27 
    28    Why support BMP format?  Well, it's a native format for Windows, and
    29    most image processing programs can read and write it.  It would be nice
    30    to be able to have at least one image format that we can natively load
    31    and save, and since PNG is so complex that it would bloat the library,
    32    BMP is a good alternative. 
    33 
    34    This code currently supports Win32 DIBs in uncompressed 8 and 24 bpp.
    35 */
    36 
    37 #include "SDL_video.h"
    38 #include "SDL_endian.h"
    39 
    40 /* Compression encodings for BMP files */
    41 #ifndef BI_RGB
    42 #define BI_RGB		0
    43 #define BI_RLE8		1
    44 #define BI_RLE4		2
    45 #define BI_BITFIELDS	3
    46 #endif
    47 
    48 
    49 SDL_Surface * 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, Gmask, Bmask, 0);
   208 	if ( surface == NULL ) {
   209 		was_error = 1;
   210 		goto done;
   211 	}
   212 
   213 	/* Load the palette, if any */
   214 	palette = (surface->format)->palette;
   215 	if ( palette ) {
   216 		if ( biClrUsed == 0 ) {
   217 			biClrUsed = 1 << biBitCount;
   218 		}
   219 		if ( biSize == 12 ) {
   220 			for ( i = 0; i < (int)biClrUsed; ++i ) {
   221 				SDL_RWread(src, &palette->colors[i].b, 1, 1);
   222 				SDL_RWread(src, &palette->colors[i].g, 1, 1);
   223 				SDL_RWread(src, &palette->colors[i].r, 1, 1);
   224 				palette->colors[i].unused = 0;
   225 			}	
   226 		} else {
   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 				SDL_RWread(src, &palette->colors[i].unused, 1, 1);
   232 			}	
   233 		}
   234 		palette->ncolors = biClrUsed;
   235 	}
   236 
   237 	/* Read the surface pixels.  Note that the bmp image is upside down */
   238 	if ( SDL_RWseek(src, fp_offset+bfOffBits, RW_SEEK_SET) < 0 ) {
   239 		SDL_Error(SDL_EFSEEK);
   240 		was_error = 1;
   241 		goto done;
   242 	}
   243 	bits = (Uint8 *)surface->pixels+(surface->h*surface->pitch);
   244 	switch (ExpandBMP) {
   245 		case 1:
   246 			bmpPitch = (biWidth + 7) >> 3;
   247 			pad  = (((bmpPitch)%4) ? (4-((bmpPitch)%4)) : 0);
   248 			break;
   249 		case 4:
   250 			bmpPitch = (biWidth + 1) >> 1;
   251 			pad  = (((bmpPitch)%4) ? (4-((bmpPitch)%4)) : 0);
   252 			break;
   253 		default:
   254 			pad  = ((surface->pitch%4) ?
   255 					(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 			Uint8 pixel = 0;
   264 			int   shift = (8-ExpandBMP);
   265 			for ( i=0; i<surface->w; ++i ) {
   266 				if ( i%(8/ExpandBMP) == 0 ) {
   267 					if ( !SDL_RWread(src, &pixel, 1, 1) ) {
   268 						SDL_SetError(
   269 					"Error reading from BMP");
   270 						was_error = 1;
   271 						goto done;
   272 					}
   273 				}
   274 				*(bits+i) = (pixel>>shift);
   275 				pixel <<= ExpandBMP;
   276 			} }
   277 			break;
   278 
   279 			default:
   280 			if ( SDL_RWread(src, bits, 1, surface->pitch)
   281 							 != surface->pitch ) {
   282 				SDL_Error(SDL_EFREAD);
   283 				was_error = 1;
   284 				goto done;
   285 			}
   286 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
   287 			/* Byte-swap the pixels if needed. Note that the 24bpp
   288 			   case has already been taken care of above. */
   289 			switch(biBitCount) {
   290 				case 15:
   291 				case 16: {
   292 				        Uint16 *pix = (Uint16 *)bits;
   293 					for(i = 0; i < surface->w; i++)
   294 					        pix[i] = SDL_Swap16(pix[i]);
   295 					break;
   296 				}
   297 
   298 				case 32: {
   299 				        Uint32 *pix = (Uint32 *)bits;
   300 					for(i = 0; i < surface->w; i++)
   301 					        pix[i] = SDL_Swap32(pix[i]);
   302 					break;
   303 				}
   304 			}
   305 #endif
   306 			break;
   307 		}
   308 		/* Skip padding bytes, ugh */
   309 		if ( pad ) {
   310 			Uint8 padbyte;
   311 			for ( i=0; i<pad; ++i ) {
   312 				SDL_RWread(src, &padbyte, 1, 1);
   313 			}
   314 		}
   315 	}
   316 done:
   317 	if ( was_error ) {
   318 		if ( src ) {
   319 			SDL_RWseek(src, fp_offset, RW_SEEK_SET);
   320 		}
   321 		if ( surface ) {
   322 			SDL_FreeSurface(surface);
   323 		}
   324 		surface = NULL;
   325 	}
   326 	if ( freesrc && src ) {
   327 		SDL_RWclose(src);
   328 	}
   329 	return(surface);
   330 }
   331 
   332 int SDL_SaveBMP_RW (SDL_Surface *saveme, SDL_RWops *dst, int freedst)
   333 {
   334 	long fp_offset;
   335 	int i, pad;
   336 	SDL_Surface *surface;
   337 	Uint8 *bits;
   338 
   339 	/* The Win32 BMP file header (14 bytes) */
   340 	char   magic[2] = { 'B', 'M' };
   341 	Uint32 bfSize;
   342 	Uint16 bfReserved1;
   343 	Uint16 bfReserved2;
   344 	Uint32 bfOffBits;
   345 
   346 	/* The Win32 BITMAPINFOHEADER struct (40 bytes) */
   347 	Uint32 biSize;
   348 	Sint32 biWidth;
   349 	Sint32 biHeight;
   350 	Uint16 biPlanes;
   351 	Uint16 biBitCount;
   352 	Uint32 biCompression;
   353 	Uint32 biSizeImage;
   354 	Sint32 biXPelsPerMeter;
   355 	Sint32 biYPelsPerMeter;
   356 	Uint32 biClrUsed;
   357 	Uint32 biClrImportant;
   358 
   359 	/* Make sure we have somewhere to save */
   360 	surface = NULL;
   361 	if ( dst ) {
   362 		if ( saveme->format->palette ) {
   363 			if ( saveme->format->BitsPerPixel == 8 ) {
   364 				surface = saveme;
   365 			} else {
   366 				SDL_SetError("%d bpp BMP files not supported",
   367 						saveme->format->BitsPerPixel);
   368 			}
   369 		}
   370 		else if ( (saveme->format->BitsPerPixel == 24) &&
   371 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
   372 				(saveme->format->Rmask == 0x00FF0000) &&
   373 				(saveme->format->Gmask == 0x0000FF00) &&
   374 				(saveme->format->Bmask == 0x000000FF)
   375 #else
   376 				(saveme->format->Rmask == 0x000000FF) &&
   377 				(saveme->format->Gmask == 0x0000FF00) &&
   378 				(saveme->format->Bmask == 0x00FF0000)
   379 #endif
   380 			  ) {
   381 			surface = saveme;
   382 		} else {
   383 			SDL_Rect bounds;
   384 
   385 			/* Convert to 24 bits per pixel */
   386 			surface = SDL_CreateRGBSurface(SDL_SWSURFACE,
   387 					saveme->w, saveme->h, 24,
   388 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
   389 					0x00FF0000, 0x0000FF00, 0x000000FF,
   390 #else
   391 					0x000000FF, 0x0000FF00, 0x00FF0000,
   392 #endif
   393 					0);
   394 			if ( surface != NULL ) {
   395 				bounds.x = 0;
   396 				bounds.y = 0;
   397 				bounds.w = saveme->w;
   398 				bounds.h = saveme->h;
   399 				if ( SDL_LowerBlit(saveme, &bounds, surface,
   400 							&bounds) < 0 ) {
   401 					SDL_FreeSurface(surface);
   402 					SDL_SetError(
   403 					"Couldn't convert image to 24 bpp");
   404 					surface = NULL;
   405 				}
   406 			}
   407 		}
   408 	}
   409 
   410 	if ( surface && (SDL_LockSurface(surface) == 0) ) {
   411 		const int bw = surface->w*surface->format->BytesPerPixel;
   412 
   413 		/* Set the BMP file header values */
   414 		bfSize = 0;		 /* We'll write this when we're done */
   415 		bfReserved1 = 0;
   416 		bfReserved2 = 0;
   417 		bfOffBits = 0;		/* We'll write this when we're done */
   418 
   419 		/* Write the BMP file header values */
   420 		fp_offset = SDL_RWtell(dst);
   421 		SDL_ClearError();
   422 		SDL_RWwrite(dst, magic, 2, 1);
   423 		SDL_WriteLE32(dst, bfSize);
   424 		SDL_WriteLE16(dst, bfReserved1);
   425 		SDL_WriteLE16(dst, bfReserved2);
   426 		SDL_WriteLE32(dst, bfOffBits);
   427 
   428 		/* Set the BMP info values */
   429 		biSize = 40;
   430 		biWidth = surface->w;
   431 		biHeight = surface->h;
   432 		biPlanes = 1;
   433 		biBitCount = surface->format->BitsPerPixel;
   434 		biCompression = BI_RGB;
   435 		biSizeImage = surface->h*surface->pitch;
   436 		biXPelsPerMeter = 0;
   437 		biYPelsPerMeter = 0;
   438 		if ( surface->format->palette ) {
   439 			biClrUsed = surface->format->palette->ncolors;
   440 		} else {
   441 			biClrUsed = 0;
   442 		}
   443 		biClrImportant = 0;
   444 
   445 		/* Write the BMP info values */
   446 		SDL_WriteLE32(dst, biSize);
   447 		SDL_WriteLE32(dst, biWidth);
   448 		SDL_WriteLE32(dst, biHeight);
   449 		SDL_WriteLE16(dst, biPlanes);
   450 		SDL_WriteLE16(dst, biBitCount);
   451 		SDL_WriteLE32(dst, biCompression);
   452 		SDL_WriteLE32(dst, biSizeImage);
   453 		SDL_WriteLE32(dst, biXPelsPerMeter);
   454 		SDL_WriteLE32(dst, biYPelsPerMeter);
   455 		SDL_WriteLE32(dst, biClrUsed);
   456 		SDL_WriteLE32(dst, biClrImportant);
   457 
   458 		/* Write the palette (in BGR color order) */
   459 		if ( surface->format->palette ) {
   460 			SDL_Color *colors;
   461 			int       ncolors;
   462 
   463 			colors = surface->format->palette->colors;
   464 			ncolors = surface->format->palette->ncolors;
   465 			for ( i=0; i<ncolors; ++i ) {
   466 				SDL_RWwrite(dst, &colors[i].b, 1, 1);
   467 				SDL_RWwrite(dst, &colors[i].g, 1, 1);
   468 				SDL_RWwrite(dst, &colors[i].r, 1, 1);
   469 				SDL_RWwrite(dst, &colors[i].unused, 1, 1);
   470 			}
   471 		}
   472 
   473 		/* Write the bitmap offset */
   474 		bfOffBits = SDL_RWtell(dst)-fp_offset;
   475 		if ( SDL_RWseek(dst, fp_offset+10, RW_SEEK_SET) < 0 ) {
   476 			SDL_Error(SDL_EFSEEK);
   477 		}
   478 		SDL_WriteLE32(dst, bfOffBits);
   479 		if ( SDL_RWseek(dst, fp_offset+bfOffBits, RW_SEEK_SET) < 0 ) {
   480 			SDL_Error(SDL_EFSEEK);
   481 		}
   482 
   483 		/* Write the bitmap image upside down */
   484 		bits = (Uint8 *)surface->pixels+(surface->h*surface->pitch);
   485 		pad  = ((bw%4) ? (4-(bw%4)) : 0);
   486 		while ( bits > (Uint8 *)surface->pixels ) {
   487 			bits -= surface->pitch;
   488 			if ( SDL_RWwrite(dst, bits, 1, bw) != bw) {
   489 				SDL_Error(SDL_EFWRITE);
   490 				break;
   491 			}
   492 			if ( pad ) {
   493 				const Uint8 padbyte = 0;
   494 				for ( i=0; i<pad; ++i ) {
   495 					SDL_RWwrite(dst, &padbyte, 1, 1);
   496 				}
   497 			}
   498 		}
   499 
   500 		/* Write the BMP file size */
   501 		bfSize = SDL_RWtell(dst)-fp_offset;
   502 		if ( SDL_RWseek(dst, fp_offset+2, RW_SEEK_SET) < 0 ) {
   503 			SDL_Error(SDL_EFSEEK);
   504 		}
   505 		SDL_WriteLE32(dst, bfSize);
   506 		if ( SDL_RWseek(dst, fp_offset+bfSize, RW_SEEK_SET) < 0 ) {
   507 			SDL_Error(SDL_EFSEEK);
   508 		}
   509 
   510 		/* Close it up.. */
   511 		SDL_UnlockSurface(surface);
   512 		if ( surface != saveme ) {
   513 			SDL_FreeSurface(surface);
   514 		}
   515 	}
   516 
   517 	if ( freedst && dst ) {
   518 		SDL_RWclose(dst);
   519 	}
   520 	return((SDL_strcmp(SDL_GetError(), "") == 0) ? 0 : -1);
   521 }
   522 
   523 #endif /* ENABLE_FILE */