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