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