IMG_xpm.c
author Sam Lantinga <slouken@libsdl.org>
Mon, 08 Dec 2008 00:27:32 +0000
changeset 186 5fbbeabf647f
parent 176 80d451f56922
child 236 cce1251de477
permissions -rw-r--r--
Updated copyright date
     1 /*
     2     SDL_image:  An example image loading library for use with SDL
     3     Copyright (C) 1997-2009 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 /*
    24  * XPM (X PixMap) image loader:
    25  *
    26  * Supports the XPMv3 format, EXCEPT:
    27  * - hotspot coordinates are ignored
    28  * - only colour ('c') colour symbols are used
    29  * - rgb.txt is not used (for portability), so only RGB colours
    30  *   are recognized (#rrggbb etc) - only a few basic colour names are
    31  *   handled
    32  *
    33  * The result is an 8bpp indexed surface if possible, otherwise 32bpp.
    34  * The colourkey is correctly set if transparency is used.
    35  * 
    36  * Besides the standard API, also provides
    37  *
    38  *     SDL_Surface *IMG_ReadXPMFromArray(char **xpm)
    39  *
    40  * that reads the image data from an XPM file included in the C source.
    41  *
    42  * TODO: include rgb.txt here. The full table (from solaris 2.6) only
    43  * requires about 13K in binary form.
    44  */
    45 
    46 #include <stdlib.h>
    47 #include <stdio.h>
    48 #include <string.h>
    49 #include <ctype.h>
    50 
    51 #include "SDL_image.h"
    52 
    53 #ifdef LOAD_XPM
    54 
    55 /* See if an image is contained in a data source */
    56 int IMG_isXPM(SDL_RWops *src)
    57 {
    58 	int start;
    59 	int is_XPM;
    60 	char magic[9];
    61 
    62 	if ( !src )
    63 		return 0;
    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 	int start = 0;
   314 	SDL_Surface *image = NULL;
   315 	int index;
   316 	int x, y;
   317 	int w, h, ncolors, cpp;
   318 	int indexed;
   319 	Uint8 *dst;
   320 	struct color_hash *colors = NULL;
   321 	SDL_Color *im_colors = NULL;
   322 	char *keystrings = NULL, *nextkey;
   323 	char *line;
   324 	char ***xpmlines = NULL;
   325 	int pixels_len;
   326 
   327 	error = NULL;
   328 	linebuf = NULL;
   329 	buflen = 0;
   330 
   331 	if ( src ) 
   332 		start = SDL_RWtell(src);
   333 
   334 	if(xpm)
   335 		xpmlines = &xpm;
   336 
   337 	line = get_next_line(xpmlines, src, 0);
   338 	if(!line)
   339 		goto done;
   340 	/*
   341 	 * The header string of an XPMv3 image has the format
   342 	 *
   343 	 * <width> <height> <ncolors> <cpp> [ <hotspot_x> <hotspot_y> ]
   344 	 *
   345 	 * where the hotspot coords are intended for mouse cursors.
   346 	 * Right now we don't use the hotspots but it should be handled
   347 	 * one day.
   348 	 */
   349 	if(sscanf(line, "%d %d %d %d", &w, &h, &ncolors, &cpp) != 4
   350 	   || w <= 0 || h <= 0 || ncolors <= 0 || cpp <= 0) {
   351 		error = "Invalid format description";
   352 		goto done;
   353 	}
   354 
   355 	keystrings = malloc(ncolors * cpp);
   356 	if(!keystrings) {
   357 		error = "Out of memory";
   358 		goto done;
   359 	}
   360 	nextkey = keystrings;
   361 
   362 	/* Create the new surface */
   363 	if(ncolors <= 256) {
   364 		indexed = 1;
   365 		image = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 8,
   366 					     0, 0, 0, 0);
   367 		im_colors = image->format->palette->colors;
   368 		image->format->palette->ncolors = ncolors;
   369 	} else {
   370 		indexed = 0;
   371 		image = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32,
   372 					     0xff0000, 0x00ff00, 0x0000ff, 0);
   373 	}
   374 	if(!image) {
   375 		/* Hmm, some SDL error (out of memory?) */
   376 		goto done;
   377 	}
   378 
   379 	/* Read the colors */
   380 	colors = create_colorhash(ncolors);
   381 	if (!colors) {
   382 		error = "Out of memory";
   383 		goto done;
   384 	}
   385 	for(index = 0; index < ncolors; ++index ) {
   386 		char *p;
   387 		line = get_next_line(xpmlines, src, 0);
   388 		if(!line)
   389 			goto done;
   390 
   391 		p = line + cpp + 1;
   392 
   393 		/* parse a colour definition */
   394 		for(;;) {
   395 			char nametype;
   396 			char *colname;
   397 			Uint32 rgb, pixel;
   398 
   399 			SKIPSPACE(p);
   400 			if(!*p) {
   401 				error = "colour parse error";
   402 				goto done;
   403 			}
   404 			nametype = *p;
   405 			SKIPNONSPACE(p);
   406 			SKIPSPACE(p);
   407 			colname = p;
   408 			SKIPNONSPACE(p);
   409 			if(nametype == 's')
   410 				continue;      /* skip symbolic colour names */
   411 
   412 			if(!color_to_rgb(colname, p - colname, &rgb))
   413 				continue;
   414 
   415 			memcpy(nextkey, line, cpp);
   416 			if(indexed) {
   417 				SDL_Color *c = im_colors + index;
   418 				c->r = (Uint8)(rgb >> 16);
   419 				c->g = (Uint8)(rgb >> 8);
   420 				c->b = (Uint8)(rgb);
   421 				pixel = index;
   422 			} else
   423 				pixel = rgb;
   424 			add_colorhash(colors, nextkey, cpp, pixel);
   425 			nextkey += cpp;
   426 			if(rgb == 0xffffffff)
   427 				SDL_SetColorKey(image, SDL_SRCCOLORKEY, pixel);
   428 			break;
   429 		}
   430 	}
   431 
   432 	/* Read the pixels */
   433 	pixels_len = w * cpp;
   434 	dst = image->pixels;
   435 	for(y = 0; y < h; y++) {
   436 		line = get_next_line(xpmlines, src, pixels_len);
   437 		if(indexed) {
   438 			/* optimization for some common cases */
   439 			if(cpp == 1)
   440 				for(x = 0; x < w; x++)
   441 					dst[x] = (Uint8)QUICK_COLORHASH(colors,
   442 								 line + x);
   443 			else
   444 				for(x = 0; x < w; x++)
   445 					dst[x] = (Uint8)get_colorhash(colors,
   446 							       line + x * cpp,
   447 							       cpp);
   448 		} else {
   449 			for (x = 0; x < w; x++)
   450 				((Uint32*)dst)[x] = get_colorhash(colors,
   451 								line + x * cpp,
   452 								  cpp);
   453 		}
   454 		dst += image->pitch;
   455 	}
   456 
   457 done:
   458 	if(error) {
   459 		if ( src )
   460 			SDL_RWseek(src, start, SEEK_SET);
   461 		if ( image ) {
   462 			SDL_FreeSurface(image);
   463 			image = NULL;
   464 		}
   465 		IMG_SetError(error);
   466 	}
   467 	free(keystrings);
   468 	free_colorhash(colors);
   469 	free(linebuf);
   470 	return(image);
   471 }
   472 
   473 /* Load a XPM type image from an RWops datasource */
   474 SDL_Surface *IMG_LoadXPM_RW(SDL_RWops *src)
   475 {
   476 	if ( !src ) {
   477 		/* The error message has been set in SDL_RWFromFile */
   478 		return NULL;
   479 	}
   480 	return load_xpm(NULL, src);
   481 }
   482 
   483 SDL_Surface *IMG_ReadXPMFromArray(char **xpm)
   484 {
   485 	return load_xpm(xpm, NULL);
   486 }
   487 
   488 #else  /* not LOAD_XPM */
   489 
   490 /* See if an image is contained in a data source */
   491 int IMG_isXPM(SDL_RWops *src)
   492 {
   493 	return(0);
   494 }
   495 
   496 
   497 /* Load a XPM type image from an SDL datasource */
   498 SDL_Surface *IMG_LoadXPM_RW(SDL_RWops *src)
   499 {
   500 	return(NULL);
   501 }
   502 
   503 SDL_Surface *IMG_ReadXPMFromArray(char **xpm)
   504 {
   505     return NULL;
   506 }
   507 #endif /* not LOAD_XPM */