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