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