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