IMG_bmp.c
author Ryan C. Gordon <icculus@icculus.org>
Tue, 13 Feb 2007 10:09:17 +0000
changeset 154 201cc5c1b373
parent 151 d2452c2421bd
child 184 578a70f2c6d7
permissions -rw-r--r--
SDL_image shouldn't dereference a rwops if it's NULL.

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