IMG_bmp.c
author Sam Lantinga <slouken@libsdl.org>
Sun, 14 May 2006 20:57:19 +0000
changeset 151 d2452c2421bd
parent 121 1bf9c0c87374
child 154 201cc5c1b373
permissions -rw-r--r--
Maurizio Monge - Sun May 14 13:57:32 PDT 2006
* Fixed loading BMP palettes at unusual offsets
     1 /*
     2     SDL_image:  An example image loading library for use with SDL
     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 /* This is a BMP image file loading framework */
    24 
    25 #include <stdio.h>
    26 #include <string.h>
    27 
    28 #include "SDL_image.h"
    29 
    30 #ifdef LOAD_BMP
    31 
    32 /* See if an image is contained in a data source */
    33 int IMG_isBMP(SDL_RWops *src)
    34 {
    35 	int start;
    36 	int is_BMP;
    37 	char magic[2];
    38 
    39 	start = SDL_RWtell(src);
    40 	is_BMP = 0;
    41 	if ( SDL_RWread(src, magic, sizeof(magic), 1) ) {
    42 		if ( strncmp(magic, "BM", 2) == 0 ) {
    43 			is_BMP = 1;
    44 		}
    45 	}
    46 	SDL_RWseek(src, start, SEEK_SET);
    47 	return(is_BMP);
    48 }
    49 
    50 #include "SDL_error.h"
    51 #include "SDL_video.h"
    52 #include "SDL_endian.h"
    53 
    54 /* Compression encodings for BMP files */
    55 #ifndef BI_RGB
    56 #define BI_RGB		0
    57 #define BI_RLE8		1
    58 #define BI_RLE4		2
    59 #define BI_BITFIELDS	3
    60 #endif
    61 
    62 static int readRlePixels(SDL_Surface * surface, SDL_RWops * src, int isRle8)
    63 {
    64 	/*
    65 	| Sets the surface pixels from src.  A bmp image is upside down.
    66 	*/
    67 	int pitch = surface->pitch;
    68 	int height = surface->h;
    69 	Uint8 * bits = (Uint8 *)surface->pixels + ((height-1) * pitch);
    70 	int ofs = 0;
    71 	Uint8 ch;
    72 	Uint8 needsPad;
    73 
    74 	for (;;) {
    75 		if ( !SDL_RWread(src, &ch, 1, 1) ) return 1;
    76 		/*
    77 		| encoded mode starts with a run length, and then a byte
    78 		| with two colour indexes to alternate between for the run
    79 		*/
    80 		if ( ch ) {
    81 			Uint8 pixel;
    82 			if ( !SDL_RWread(src, &pixel, 1, 1) ) return 1;
    83 			if ( isRle8 ) {                 /* 256-color bitmap, compressed */
    84 				do {
    85 					bits[ofs++] = pixel;
    86 				} while (--ch);
    87 			}else {                         /* 16-color bitmap, compressed */
    88 				Uint8 pixel0 = pixel >> 4;
    89 				Uint8 pixel1 = pixel & 0x0F;
    90 				for (;;) {
    91 					bits[ofs++] = pixel0;     /* even count, high nibble */
    92 					if (!--ch) break;
    93 					bits[ofs++] = pixel1;     /* odd count, low nibble */
    94 					if (!--ch) break;
    95 				}
    96 			}
    97 		} else {
    98 			/*
    99 			| A leading zero is an escape; it may signal the end of the bitmap,
   100 			| a cursor move, or some absolute data.
   101 			| zero tag may be absolute mode or an escape
   102 			*/
   103 			if ( !SDL_RWread(src, &ch, 1, 1) ) return 1;
   104 			switch (ch) {
   105 			case 0:                         /* end of line */
   106 				ofs = 0;
   107 				bits -= pitch;               /* go to previous */
   108 				break;
   109 			case 1:                         /* end of bitmap */
   110 				return 0;                    /* success! */
   111 			case 2:                         /* delta */
   112 				if ( !SDL_RWread(src, &ch, 1, 1) ) return 1;
   113 				ofs += ch;
   114 				if ( !SDL_RWread(src, &ch, 1, 1) ) return 1;
   115 				bits -= (ch * pitch);
   116 				break;
   117 			default:                        /* no compression */
   118 				if (isRle8) {
   119 					needsPad = ( ch & 1 );
   120 					do {
   121 						if ( !SDL_RWread(src, bits + ofs++, 1, 1) ) return 1;
   122 					} while (--ch);
   123 				} else {
   124 					needsPad = ( ((ch+1)>>1) & 1 ); /* (ch+1)>>1: bytes size */
   125 					for (;;) {
   126 						Uint8 pixel;
   127 						if ( !SDL_RWread(src, &pixel, 1, 1) ) return 1;
   128 						bits[ofs++] = pixel >> 4;
   129 						if (!--ch) break;
   130 						bits[ofs++] = pixel & 0x0F;
   131 						if (!--ch) break;
   132 					}
   133 				}
   134 				/* pad at even boundary */
   135 				if ( needsPad && !SDL_RWread(src, &ch, 1, 1) ) return 1;
   136 				break;
   137 			}
   138 		}
   139 	}
   140 }
   141 
   142 static SDL_Surface *LoadBMP_RW (SDL_RWops *src, int freesrc)
   143 {
   144 	int was_error;
   145 	long fp_offset;
   146 	int bmpPitch;
   147 	int i, pad;
   148 	SDL_Surface *surface;
   149 	Uint32 Rmask;
   150 	Uint32 Gmask;
   151 	Uint32 Bmask;
   152 	Uint32 Amask;
   153 	SDL_Palette *palette;
   154 	Uint8 *bits;
   155 	int ExpandBMP;
   156 
   157 	/* The Win32 BMP file header (14 bytes) */
   158 	char   magic[2];
   159 	Uint32 bfSize;
   160 	Uint16 bfReserved1;
   161 	Uint16 bfReserved2;
   162 	Uint32 bfOffBits;
   163 
   164 	/* The Win32 BITMAPINFOHEADER struct (40 bytes) */
   165 	Uint32 biSize;
   166 	Sint32 biWidth;
   167 	Sint32 biHeight;
   168 	Uint16 biPlanes;
   169 	Uint16 biBitCount;
   170 	Uint32 biCompression;
   171 	Uint32 biSizeImage;
   172 	Sint32 biXPelsPerMeter;
   173 	Sint32 biYPelsPerMeter;
   174 	Uint32 biClrUsed;
   175 	Uint32 biClrImportant;
   176 
   177 	/* Make sure we are passed a valid data source */
   178 	surface = NULL;
   179 	was_error = 0;
   180 	if ( src == NULL ) {
   181 		was_error = 1;
   182 		goto done;
   183 	}
   184 
   185 	/* Read in the BMP file header */
   186 	fp_offset = SDL_RWtell(src);
   187 	SDL_ClearError();
   188 	if ( SDL_RWread(src, magic, 1, 2) != 2 ) {
   189 		SDL_Error(SDL_EFREAD);
   190 		was_error = 1;
   191 		goto done;
   192 	}
   193 	if ( strncmp(magic, "BM", 2) != 0 ) {
   194 		SDL_SetError("File is not a Windows BMP file");
   195 		was_error = 1;
   196 		goto done;
   197 	}
   198 	bfSize		= SDL_ReadLE32(src);
   199 	bfReserved1	= SDL_ReadLE16(src);
   200 	bfReserved2	= SDL_ReadLE16(src);
   201 	bfOffBits	= SDL_ReadLE32(src);
   202 
   203 	/* Read the Win32 BITMAPINFOHEADER */
   204 	biSize		= SDL_ReadLE32(src);
   205 	if ( biSize == 12 ) {
   206 		biWidth		= (Uint32)SDL_ReadLE16(src);
   207 		biHeight	= (Uint32)SDL_ReadLE16(src);
   208 		biPlanes	= SDL_ReadLE16(src);
   209 		biBitCount	= SDL_ReadLE16(src);
   210 		biCompression	= BI_RGB;
   211 		biSizeImage	= 0;
   212 		biXPelsPerMeter	= 0;
   213 		biYPelsPerMeter	= 0;
   214 		biClrUsed	= 0;
   215 		biClrImportant	= 0;
   216 	} else {
   217 		biWidth		= SDL_ReadLE32(src);
   218 		biHeight	= SDL_ReadLE32(src);
   219 		biPlanes	= SDL_ReadLE16(src);
   220 		biBitCount	= SDL_ReadLE16(src);
   221 		biCompression	= SDL_ReadLE32(src);
   222 		biSizeImage	= SDL_ReadLE32(src);
   223 		biXPelsPerMeter	= SDL_ReadLE32(src);
   224 		biYPelsPerMeter	= SDL_ReadLE32(src);
   225 		biClrUsed	= SDL_ReadLE32(src);
   226 		biClrImportant	= SDL_ReadLE32(src);
   227 	}
   228 
   229 	/* Check for read error */
   230 	if ( strcmp(SDL_GetError(), "") != 0 ) {
   231 		was_error = 1;
   232 		goto done;
   233 	}
   234 
   235 	/* Expand 1 and 4 bit bitmaps to 8 bits per pixel */
   236 	switch (biBitCount) {
   237 		case 1:
   238 		case 4:
   239 			ExpandBMP = biBitCount;
   240 			biBitCount = 8;
   241 			break;
   242 		default:
   243 			ExpandBMP = 0;
   244 			break;
   245 	}
   246 
   247 	/* RLE4 and RLE8 BMP compression is supported */
   248 	Rmask = Gmask = Bmask = Amask = 0;
   249 	switch (biCompression) {
   250 		case BI_RGB:
   251 			/* If there are no masks, use the defaults */
   252 			if ( bfOffBits == (14+biSize) ) {
   253 				/* Default values for the BMP format */
   254 				switch (biBitCount) {
   255 					case 15:
   256 					case 16:
   257 						Rmask = 0x7C00;
   258 						Gmask = 0x03E0;
   259 						Bmask = 0x001F;
   260 						break;
   261 					case 24:
   262 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
   263 					        Rmask = 0x000000FF;
   264 					        Gmask = 0x0000FF00;
   265 					        Bmask = 0x00FF0000;
   266 #else
   267 						Rmask = 0x00FF0000;
   268 						Gmask = 0x0000FF00;
   269 						Bmask = 0x000000FF;
   270 #endif
   271 						break;
   272 					case 32:
   273 						Amask = 0xFF000000;
   274 						Rmask = 0x00FF0000;
   275 						Gmask = 0x0000FF00;
   276 						Bmask = 0x000000FF;
   277 						break;
   278 					default:
   279 						break;
   280 				}
   281 				break;
   282 			}
   283 			/* Fall through -- read the RGB masks */
   284 
   285 		default:
   286 			switch (biBitCount) {
   287 				case 15:
   288 				case 16:
   289 				case 32:
   290 					Rmask = SDL_ReadLE32(src);
   291 					Gmask = SDL_ReadLE32(src);
   292 					Bmask = SDL_ReadLE32(src);
   293 					Amask = SDL_ReadLE32(src);
   294 					break;
   295 				default:
   296 					break;
   297 			}
   298 			break;
   299 	}
   300 
   301 	/* Create a compatible surface, note that the colors are RGB ordered */
   302 	surface = SDL_CreateRGBSurface(SDL_SWSURFACE,
   303 			biWidth, biHeight, biBitCount, Rmask, Gmask, Bmask, Amask);
   304 	if ( surface == NULL ) {
   305 		was_error = 1;
   306 		goto done;
   307 	}
   308 
   309 	/* Load the palette, if any */
   310 	palette = (surface->format)->palette;
   311 	if ( palette ) {
   312 		if ( SDL_RWseek(src, fp_offset+14+biSize, SEEK_SET) < 0 ) {
   313 			SDL_Error(SDL_EFSEEK);
   314 			was_error = 1;
   315 			goto done;
   316 		}
   317 
   318 		/*
   319 		| guich: always use 1<<bpp b/c some bitmaps can bring wrong information
   320 		| for colorsUsed
   321 		*/
   322 		/* if ( biClrUsed == 0 ) {  */
   323 		biClrUsed = 1 << biBitCount;
   324 		/* } */
   325 		if ( biSize == 12 ) {
   326 			for ( i = 0; i < (int)biClrUsed; ++i ) {
   327 				SDL_RWread(src, &palette->colors[i].b, 1, 1);
   328 				SDL_RWread(src, &palette->colors[i].g, 1, 1);
   329 				SDL_RWread(src, &palette->colors[i].r, 1, 1);
   330 				palette->colors[i].unused = 0;
   331 			}	
   332 		} else {
   333 			for ( i = 0; i < (int)biClrUsed; ++i ) {
   334 				SDL_RWread(src, &palette->colors[i].b, 1, 1);
   335 				SDL_RWread(src, &palette->colors[i].g, 1, 1);
   336 				SDL_RWread(src, &palette->colors[i].r, 1, 1);
   337 				SDL_RWread(src, &palette->colors[i].unused, 1, 1);
   338 			}	
   339 		}
   340 		palette->ncolors = biClrUsed;
   341 	}
   342 
   343 	/* Read the surface pixels.  Note that the bmp image is upside down */
   344 	if ( SDL_RWseek(src, fp_offset+bfOffBits, SEEK_SET) < 0 ) {
   345 		SDL_Error(SDL_EFSEEK);
   346 		was_error = 1;
   347 		goto done;
   348 	}
   349 	if ((biCompression == BI_RLE4) || (biCompression == BI_RLE8)) {
   350 		was_error = readRlePixels(surface, src, biCompression == BI_RLE8);
   351 		if (was_error) SDL_SetError("Error reading from BMP");
   352 		goto done;
   353 	}
   354 	bits = (Uint8 *)surface->pixels+(surface->h*surface->pitch);
   355 	switch (ExpandBMP) {
   356 		case 1:
   357 			bmpPitch = (biWidth + 7) >> 3;
   358 			pad  = (((bmpPitch)%4) ? (4-((bmpPitch)%4)) : 0);
   359 			break;
   360 		case 4:
   361 			bmpPitch = (biWidth + 1) >> 1;
   362 			pad  = (((bmpPitch)%4) ? (4-((bmpPitch)%4)) : 0);
   363 			break;
   364 		default:
   365 			pad  = ((surface->pitch%4) ?
   366 					(4-(surface->pitch%4)) : 0);
   367 			break;
   368 	}
   369 	while ( bits > (Uint8 *)surface->pixels ) {
   370 		bits -= surface->pitch;
   371 		switch (ExpandBMP) {
   372 			case 1:
   373 			case 4: {
   374 			Uint8 pixel = 0;
   375 			int   shift = (8-ExpandBMP);
   376 			for ( i=0; i<surface->w; ++i ) {
   377 				if ( i%(8/ExpandBMP) == 0 ) {
   378 					if ( !SDL_RWread(src, &pixel, 1, 1) ) {
   379 						SDL_SetError(
   380 					"Error reading from BMP");
   381 						was_error = 1;
   382 						goto done;
   383 					}
   384 				}
   385 				*(bits+i) = (pixel>>shift);
   386 				pixel <<= ExpandBMP;
   387 			} }
   388 			break;
   389 
   390 			default:
   391 			if ( SDL_RWread(src, bits, 1, surface->pitch)
   392 							 != surface->pitch ) {
   393 				SDL_Error(SDL_EFREAD);
   394 				was_error = 1;
   395 				goto done;
   396 			}
   397 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
   398 			/* Byte-swap the pixels if needed. Note that the 24bpp
   399 			   case has already been taken care of above. */
   400 			switch(biBitCount) {
   401 				case 15:
   402 				case 16: {
   403 				        Uint16 *pix = (Uint16 *)bits;
   404 					for(i = 0; i < surface->w; i++)
   405 					        pix[i] = SDL_Swap16(pix[i]);
   406 					break;
   407 				}
   408 
   409 				case 32: {
   410 				        Uint32 *pix = (Uint32 *)bits;
   411 					for(i = 0; i < surface->w; i++)
   412 					        pix[i] = SDL_Swap32(pix[i]);
   413 					break;
   414 				}
   415 			}
   416 #endif
   417 			break;
   418 		}
   419 		/* Skip padding bytes, ugh */
   420 		if ( pad ) {
   421 			Uint8 padbyte;
   422 			for ( i=0; i<pad; ++i ) {
   423 				SDL_RWread(src, &padbyte, 1, 1);
   424 			}
   425 		}
   426 	}
   427 done:
   428 	if ( was_error ) {
   429 		if ( src ) {
   430 			SDL_RWseek(src, fp_offset, SEEK_SET);
   431 		}
   432 		if ( surface ) {
   433 			SDL_FreeSurface(surface);
   434 		}
   435 		surface = NULL;
   436 	}
   437 	if ( freesrc && src ) {
   438 		SDL_RWclose(src);
   439 	}
   440 	return(surface);
   441 }
   442 
   443 /* Load a BMP type image from an SDL datasource */
   444 SDL_Surface *IMG_LoadBMP_RW(SDL_RWops *src)
   445 {
   446 	return(LoadBMP_RW(src, 0));
   447 }
   448 
   449 #else
   450 
   451 /* See if an image is contained in a data source */
   452 int IMG_isBMP(SDL_RWops *src)
   453 {
   454 	return(0);
   455 }
   456 
   457 /* Load a BMP type image from an SDL datasource */
   458 SDL_Surface *IMG_LoadBMP_RW(SDL_RWops *src)
   459 {
   460 	return(NULL);
   461 }
   462 
   463 #endif /* LOAD_BMP */