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