IMG_xpm.c
author Sam Lantinga <slouken@libsdl.org>
Sat, 04 Feb 2006 20:37:17 +0000
changeset 117 e613cf987897
parent 98 9f94c4674cc9
child 118 c5e736a47ad2
permissions -rw-r--r--
Fixed image type functions so they seek back to where they started
     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 /*
    26  * XPM (X PixMap) image loader:
    27  *
    28  * Supports the XPMv3 format, EXCEPT:
    29  * - hotspot coordinates are ignored
    30  * - only colour ('c') colour symbols are used
    31  * - rgb.txt is not used (for portability), so only RGB colours
    32  *   are recognized (#rrggbb etc) - only a few basic colour names are
    33  *   handled
    34  *
    35  * The result is an 8bpp indexed surface if possible, otherwise 32bpp.
    36  * The colourkey is correctly set if transparency is used.
    37  * 
    38  * Besides the standard API, also provides
    39  *
    40  *     SDL_Surface *IMG_ReadXPMFromArray(char **xpm)
    41  *
    42  * that reads the image data from an XPM file included in the C source.
    43  *
    44  * TODO: include rgb.txt here. The full table (from solaris 2.6) only
    45  * requires about 13K in binary form.
    46  */
    47 
    48 #include <stdlib.h>
    49 #include <stdio.h>
    50 #include <string.h>
    51 #include <ctype.h>
    52 
    53 #include "SDL_image.h"
    54 
    55 #ifdef LOAD_XPM
    56 
    57 /* See if an image is contained in a data source */
    58 int IMG_isXPM(SDL_RWops *src)
    59 {
    60 	int start;
    61 	int is_XPM;
    62 	char magic[9];
    63 
    64 	start = SDL_RWtell(src);
    65 	is_XPM = 0;
    66 	if ( SDL_RWread(src, magic, sizeof(magic), 1) ) {
    67 		if ( memcmp(magic, "/* XPM */", sizeof(magic)) == 0 ) {
    68 			is_XPM = 1;
    69 		}
    70 	}
    71 	SDL_RWseek(src, start, SEEK_SET);
    72 	return(is_XPM);
    73 }
    74 
    75 /* Hash table to look up colors from pixel strings */
    76 #define STARTING_HASH_SIZE 256
    77 
    78 struct hash_entry {
    79 	char *key;
    80 	Uint32 color;
    81 	struct hash_entry *next;
    82 };
    83 
    84 struct color_hash {
    85 	struct hash_entry **table;
    86 	struct hash_entry *entries; /* array of all entries */
    87 	struct hash_entry *next_free;
    88 	int size;
    89 	int maxnum;
    90 };
    91 
    92 static int hash_key(const char *key, int cpp, int size)
    93 {
    94 	int hash;
    95 
    96 	hash = 0;
    97 	while ( cpp-- > 0 ) {
    98 		hash = hash * 33 + *key++;
    99 	}
   100 	return hash & (size - 1);
   101 }
   102 
   103 static struct color_hash *create_colorhash(int maxnum)
   104 {
   105 	int bytes, s;
   106 	struct color_hash *hash;
   107 
   108 	/* we know how many entries we need, so we can allocate
   109 	   everything here */
   110 	hash = malloc(sizeof *hash);
   111 	if(!hash)
   112 		return NULL;
   113 
   114 	/* use power-of-2 sized hash table for decoding speed */
   115 	for(s = STARTING_HASH_SIZE; s < maxnum; s <<= 1)
   116 		;
   117 	hash->size = s;
   118 	hash->maxnum = maxnum;
   119 	bytes = hash->size * sizeof(struct hash_entry **);
   120 	hash->entries = NULL;	/* in case malloc fails */
   121 	hash->table = malloc(bytes);
   122 	if(!hash->table)
   123 		return NULL;
   124 	memset(hash->table, 0, bytes);
   125 	hash->entries = malloc(maxnum * sizeof(struct hash_entry));
   126 	if(!hash->entries)
   127 		return NULL;
   128 	hash->next_free = hash->entries;
   129 	return hash;
   130 }
   131 
   132 static int add_colorhash(struct color_hash *hash,
   133                          char *key, int cpp, Uint32 color)
   134 {
   135 	int index = hash_key(key, cpp, hash->size);
   136 	struct hash_entry *e = hash->next_free++;
   137 	e->color = color;
   138 	e->key = key;
   139 	e->next = hash->table[index];
   140 	hash->table[index] = e;
   141 	return 1;
   142 }
   143 
   144 /* fast lookup that works if cpp == 1 */
   145 #define QUICK_COLORHASH(hash, key) ((hash)->table[*(Uint8 *)(key)]->color)
   146 
   147 static Uint32 get_colorhash(struct color_hash *hash, const char *key, int cpp)
   148 {
   149 	struct hash_entry *entry = hash->table[hash_key(key, cpp, hash->size)];
   150 	while(entry) {
   151 		if(memcmp(key, entry->key, cpp) == 0)
   152 			return entry->color;
   153 		entry = entry->next;
   154 	}
   155 	return 0;		/* garbage in - garbage out */
   156 }
   157 
   158 static void free_colorhash(struct color_hash *hash)
   159 {
   160 	if(hash && hash->table) {
   161 		free(hash->table);
   162 		free(hash->entries);
   163 		free(hash);
   164 	}
   165 }
   166 
   167 /* portable case-insensitive string comparison */
   168 static int string_equal(const char *a, const char *b, int n)
   169 {
   170 	while(*a && *b && n) {
   171 		if(toupper((unsigned char)*a) != toupper((unsigned char)*b))
   172 			return 0;
   173 		a++;
   174 		b++;
   175 		n--;
   176 	}
   177 	return *a == *b;
   178 }
   179 
   180 #define ARRAYSIZE(a) (int)(sizeof(a) / sizeof((a)[0]))
   181 
   182 /*
   183  * convert colour spec to RGB (in 0xrrggbb format).
   184  * return 1 if successful.
   185  */
   186 static int color_to_rgb(char *spec, int speclen, Uint32 *rgb)
   187 {
   188 	/* poor man's rgb.txt */
   189 	static struct { char *name; Uint32 rgb; } known[] = {
   190 		{"none",  0xffffffff},
   191 		{"black", 0x00000000},
   192 		{"white", 0x00ffffff},
   193 		{"red",   0x00ff0000},
   194 		{"green", 0x0000ff00},
   195 		{"blue",  0x000000ff}
   196 	};
   197 
   198 	if(spec[0] == '#') {
   199 		char buf[7];
   200 		switch(speclen) {
   201 		case 4:
   202 			buf[0] = buf[1] = spec[1];
   203 			buf[2] = buf[3] = spec[2];
   204 			buf[4] = buf[5] = spec[3];
   205 			break;
   206 		case 7:
   207 			memcpy(buf, spec + 1, 6);
   208 			break;
   209 		case 13:
   210 			buf[0] = spec[1];
   211 			buf[1] = spec[2];
   212 			buf[2] = spec[5];
   213 			buf[3] = spec[6];
   214 			buf[4] = spec[9];
   215 			buf[5] = spec[10];
   216 			break;
   217 		}
   218 		buf[6] = '\0';
   219 		*rgb = strtol(buf, NULL, 16);
   220 		return 1;
   221 	} else {
   222 		int i;
   223 		for(i = 0; i < ARRAYSIZE(known); i++)
   224 			if(string_equal(known[i].name, spec, speclen)) {
   225 				*rgb = known[i].rgb;
   226 				return 1;
   227 			}
   228 		return 0;
   229 	}
   230 }
   231 
   232 #ifndef MAX
   233 #define MAX(a, b) ((a) > (b) ? (a) : (b))
   234 #endif
   235 
   236 static char *linebuf;
   237 static int buflen;
   238 static char *error;
   239 
   240 /*
   241  * Read next line from the source.
   242  * If len > 0, it's assumed to be at least len chars (for efficiency).
   243  * Return NULL and set error upon EOF or parse error.
   244  */
   245 static char *get_next_line(char ***lines, SDL_RWops *src, int len)
   246 {
   247 	if(lines) {
   248 		return *(*lines)++;
   249 	} else {
   250 		char c;
   251 		int n;
   252 		do {
   253 			if(SDL_RWread(src, &c, 1, 1) <= 0) {
   254 				error = "Premature end of data";
   255 				return NULL;
   256 			}
   257 		} while(c != '"');
   258 		if(len) {
   259 			len += 4;	/* "\",\n\0" */
   260 			if(len > buflen){
   261 				buflen = len;
   262 				linebuf = realloc(linebuf, buflen);
   263 				if(!linebuf) {
   264 					error = "Out of memory";
   265 					return NULL;
   266 				}
   267 			}
   268 			if(SDL_RWread(src, linebuf, len - 1, 1) <= 0) {
   269 				error = "Premature end of data";
   270 				return NULL;
   271 			}
   272 			n = len - 2;
   273 		} else {
   274 			n = 0;
   275 			do {
   276 				if(n >= buflen - 1) {
   277 					if(buflen == 0)
   278 						buflen = 16;
   279 					buflen *= 2;
   280 					linebuf = realloc(linebuf, buflen);
   281 					if(!linebuf) {
   282 						error = "Out of memory";
   283 						return NULL;
   284 					}
   285 				}
   286 				if(SDL_RWread(src, linebuf + n, 1, 1) <= 0) {
   287 					error = "Premature end of data";
   288 					return NULL;
   289 				}
   290 			} while(linebuf[n++] != '"');
   291 			n--;
   292 		}
   293 		linebuf[n] = '\0';
   294 		return linebuf;
   295 	}
   296 }
   297 
   298 #define SKIPSPACE(p)				\
   299 do {						\
   300 	while(isspace((unsigned char)*(p)))	\
   301 	      ++(p);				\
   302 } while(0)
   303 
   304 #define SKIPNONSPACE(p)					\
   305 do {							\
   306 	while(!isspace((unsigned char)*(p)) && *p)	\
   307 	      ++(p);					\
   308 } while(0)
   309 
   310 /* read XPM from either array or RWops */
   311 static SDL_Surface *load_xpm(char **xpm, SDL_RWops *src)
   312 {
   313 	SDL_Surface *image = NULL;
   314 	int index;
   315 	int x, y;
   316 	int w, h, ncolors, cpp;
   317 	int indexed;
   318 	Uint8 *dst;
   319 	struct color_hash *colors = NULL;
   320 	SDL_Color *im_colors = NULL;
   321 	char *keystrings = NULL, *nextkey;
   322 	char *line;
   323 	char ***xpmlines = NULL;
   324 	int pixels_len;
   325 
   326 	error = NULL;
   327 	linebuf = NULL;
   328 	buflen = 0;
   329 
   330 	if(xpm)
   331 		xpmlines = &xpm;
   332 
   333 	line = get_next_line(xpmlines, src, 0);
   334 	if(!line)
   335 		goto done;
   336 	/*
   337 	 * The header string of an XPMv3 image has the format
   338 	 *
   339 	 * <width> <height> <ncolors> <cpp> [ <hotspot_x> <hotspot_y> ]
   340 	 *
   341 	 * where the hotspot coords are intended for mouse cursors.
   342 	 * Right now we don't use the hotspots but it should be handled
   343 	 * one day.
   344 	 */
   345 	if(sscanf(line, "%d %d %d %d", &w, &h, &ncolors, &cpp) != 4
   346 	   || w <= 0 || h <= 0 || ncolors <= 0 || cpp <= 0) {
   347 		error = "Invalid format description";
   348 		goto done;
   349 	}
   350 
   351 	keystrings = malloc(ncolors * cpp);
   352 	if(!keystrings) {
   353 		error = "Out of memory";
   354 		goto done;
   355 	}
   356 	nextkey = keystrings;
   357 
   358 	/* Create the new surface */
   359 	if(ncolors <= 256) {
   360 		indexed = 1;
   361 		image = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 8,
   362 					     0, 0, 0, 0);
   363 		im_colors = image->format->palette->colors;
   364 		image->format->palette->ncolors = ncolors;
   365 	} else {
   366 		indexed = 0;
   367 		image = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32,
   368 					     0xff0000, 0x00ff00, 0x0000ff, 0);
   369 	}
   370 	if(!image) {
   371 		/* Hmm, some SDL error (out of memory?) */
   372 		goto done;
   373 	}
   374 
   375 	/* Read the colors */
   376 	colors = create_colorhash(ncolors);
   377 	if (!colors) {
   378 		error = "Out of memory";
   379 		goto done;
   380 	}
   381 	for(index = 0; index < ncolors; ++index ) {
   382 		char *p;
   383 		line = get_next_line(xpmlines, src, 0);
   384 		if(!line)
   385 			goto done;
   386 
   387 		p = line + cpp + 1;
   388 
   389 		/* parse a colour definition */
   390 		for(;;) {
   391 			char nametype;
   392 			char *colname;
   393 			Uint32 rgb, pixel;
   394 
   395 			SKIPSPACE(p);
   396 			if(!*p) {
   397 				error = "colour parse error";
   398 				goto done;
   399 			}
   400 			nametype = *p;
   401 			SKIPNONSPACE(p);
   402 			SKIPSPACE(p);
   403 			colname = p;
   404 			SKIPNONSPACE(p);
   405 			if(nametype == 's')
   406 				continue;      /* skip symbolic colour names */
   407 
   408 			if(!color_to_rgb(colname, p - colname, &rgb))
   409 				continue;
   410 
   411 			memcpy(nextkey, line, cpp);
   412 			if(indexed) {
   413 				SDL_Color *c = im_colors + index;
   414 				c->r = rgb >> 16;
   415 				c->g = rgb >> 8;
   416 				c->b = rgb;
   417 				pixel = index;
   418 			} else
   419 				pixel = rgb;
   420 			add_colorhash(colors, nextkey, cpp, pixel);
   421 			nextkey += cpp;
   422 			if(rgb == 0xffffffff)
   423 				SDL_SetColorKey(image, SDL_SRCCOLORKEY, pixel);
   424 			break;
   425 		}
   426 	}
   427 
   428 	/* Read the pixels */
   429 	pixels_len = w * cpp;
   430 	dst = image->pixels;
   431 	for(y = 0; y < h; y++) {
   432 		line = get_next_line(xpmlines, src, pixels_len);
   433 		if(indexed) {
   434 			/* optimization for some common cases */
   435 			if(cpp == 1)
   436 				for(x = 0; x < w; x++)
   437 					dst[x] = QUICK_COLORHASH(colors,
   438 								 line + x);
   439 			else
   440 				for(x = 0; x < w; x++)
   441 					dst[x] = get_colorhash(colors,
   442 							       line + x * cpp,
   443 							       cpp);
   444 		} else {
   445 			for (x = 0; x < w; x++)
   446 				((Uint32*)dst)[x] = get_colorhash(colors,
   447 								line + x * cpp,
   448 								  cpp);
   449 		}
   450 		dst += image->pitch;
   451 	}
   452 
   453 done:
   454 	if(error) {
   455 		SDL_FreeSurface(image);
   456 		image = NULL;
   457 		IMG_SetError(error);
   458 	}
   459 	free(keystrings);
   460 	free_colorhash(colors);
   461 	free(linebuf);
   462 	return(image);
   463 }
   464 
   465 /* Load a XPM type image from an RWops datasource */
   466 SDL_Surface *IMG_LoadXPM_RW(SDL_RWops *src)
   467 {
   468 	if ( !src ) {
   469 		/* The error message has been set in SDL_RWFromFile */
   470 		return NULL;
   471 	}
   472 	return load_xpm(NULL, src);
   473 }
   474 
   475 SDL_Surface *IMG_ReadXPMFromArray(char **xpm)
   476 {
   477 	return load_xpm(xpm, NULL);
   478 }
   479 
   480 #else  /* not LOAD_XPM */
   481 
   482 /* See if an image is contained in a data source */
   483 int IMG_isXPM(SDL_RWops *src)
   484 {
   485 	return(0);
   486 }
   487 
   488 
   489 /* Load a XPM type image from an SDL datasource */
   490 SDL_Surface *IMG_LoadXPM_RW(SDL_RWops *src)
   491 {
   492 	return(NULL);
   493 }
   494 
   495 SDL_Surface *IMG_ReadXPMFromArray(char **xpm)
   496 {
   497     return NULL;
   498 }
   499 #endif /* not LOAD_XPM */