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