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