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