IMG_xpm.c
author Thomas Bernard <miniupnp@free.fr>
Fri, 30 Nov 2018 11:04:15 +0100
branchSDL-1.2
changeset 634 68f958f43339
parent 607 1a1189c2978f
permissions -rw-r--r--
IMG_xcf.c: Avoid infinite loop in read_xcf_header()
     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 = (struct color_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 = (struct hash_entry **)malloc(bytes);
   121 	if(!hash->table) {
   122 		free(hash);
   123 		return NULL;
   124 	}
   125 	memset(hash->table, 0, bytes);
   126 	hash->entries = (struct hash_entry *)malloc(maxnum * sizeof(struct hash_entry));
   127 	if(!hash->entries) {
   128 		free(hash->table);
   129 		free(hash);
   130 		return NULL;
   131 	}
   132 	hash->next_free = hash->entries;
   133 	return hash;
   134 }
   135 
   136 static int add_colorhash(struct color_hash *hash,
   137                          char *key, int cpp, Uint32 color)
   138 {
   139 	int index = hash_key(key, cpp, hash->size);
   140 	struct hash_entry *e = hash->next_free++;
   141 	e->color = color;
   142 	e->key = key;
   143 	e->next = hash->table[index];
   144 	hash->table[index] = e;
   145 	return 1;
   146 }
   147 
   148 /* fast lookup that works if cpp == 1 */
   149 #define QUICK_COLORHASH(hash, key) ((hash)->table[*(Uint8 *)(key)]->color)
   150 
   151 static Uint32 get_colorhash(struct color_hash *hash, const char *key, int cpp)
   152 {
   153 	struct hash_entry *entry = hash->table[hash_key(key, cpp, hash->size)];
   154 	while(entry) {
   155 		if(memcmp(key, entry->key, cpp) == 0)
   156 			return entry->color;
   157 		entry = entry->next;
   158 	}
   159 	return 0;		/* garbage in - garbage out */
   160 }
   161 
   162 static void free_colorhash(struct color_hash *hash)
   163 {
   164 	if(hash) {
   165 		free(hash->table);
   166 		free(hash->entries);
   167 		free(hash);
   168 	}
   169 }
   170 
   171 /* portable case-insensitive string comparison */
   172 static int string_equal(const char *a, const char *b, int n)
   173 {
   174 	while(*a && *b && n) {
   175 		if(toupper((unsigned char)*a) != toupper((unsigned char)*b))
   176 			return 0;
   177 		a++;
   178 		b++;
   179 		n--;
   180 	}
   181 	return *a == *b;
   182 }
   183 
   184 #define ARRAYSIZE(a) (int)(sizeof(a) / sizeof((a)[0]))
   185 
   186 /*
   187  * convert colour spec to RGB (in 0xrrggbb format).
   188  * return 1 if successful.
   189  */
   190 static int color_to_rgb(char *spec, int speclen, Uint32 *rgb)
   191 {
   192 	/* poor man's rgb.txt */
   193 	static struct { char *name; Uint32 rgb; } known[] = {
   194 		{"none",  0xffffffff},
   195 		{"black", 0x00000000},
   196 		{"white", 0x00ffffff},
   197 		{"red",   0x00ff0000},
   198 		{"green", 0x0000ff00},
   199 		{"blue",  0x000000ff}
   200 	};
   201 
   202 	if(spec[0] == '#') {
   203 		char buf[7];
   204 		switch(speclen) {
   205 		case 4:
   206 			buf[0] = buf[1] = spec[1];
   207 			buf[2] = buf[3] = spec[2];
   208 			buf[4] = buf[5] = spec[3];
   209 			break;
   210 		case 7:
   211 			memcpy(buf, spec + 1, 6);
   212 			break;
   213 		case 13:
   214 			buf[0] = spec[1];
   215 			buf[1] = spec[2];
   216 			buf[2] = spec[5];
   217 			buf[3] = spec[6];
   218 			buf[4] = spec[9];
   219 			buf[5] = spec[10];
   220 			break;
   221 		}
   222 		buf[6] = '\0';
   223 		*rgb = strtol(buf, NULL, 16);
   224 		return 1;
   225 	} else {
   226 		int i;
   227 		for(i = 0; i < ARRAYSIZE(known); i++)
   228 			if(string_equal(known[i].name, spec, speclen)) {
   229 				*rgb = known[i].rgb;
   230 				return 1;
   231 			}
   232 		return 0;
   233 	}
   234 }
   235 
   236 #ifndef MAX
   237 #define MAX(a, b) ((a) > (b) ? (a) : (b))
   238 #endif
   239 
   240 static char *linebuf;
   241 static int buflen;
   242 static char *error;
   243 
   244 /*
   245  * Read next line from the source.
   246  * If len > 0, it's assumed to be at least len chars (for efficiency).
   247  * Return NULL and set error upon EOF or parse error.
   248  */
   249 static char *get_next_line(char ***lines, SDL_RWops *src, int len)
   250 {
   251 	char *linebufnew;
   252 
   253 	if(lines) {
   254 		return *(*lines)++;
   255 	} else {
   256 		char c;
   257 		int n;
   258 		do {
   259 			if(SDL_RWread(src, &c, 1, 1) <= 0) {
   260 				error = "Premature end of data";
   261 				return NULL;
   262 			}
   263 		} while(c != '"');
   264 		if(len) {
   265 			len += 4;	/* "\",\n\0" */
   266 			if(len > buflen){
   267 				buflen = len;
   268 				linebufnew = (char *)realloc(linebuf, buflen);
   269 				if(!linebufnew) {
   270 					free(linebuf);
   271 					error = "Out of memory";
   272 					return NULL;
   273 				}
   274 				linebuf = linebufnew;
   275 			}
   276 			if(SDL_RWread(src, linebuf, len - 1, 1) <= 0) {
   277 				error = "Premature end of data";
   278 				return NULL;
   279 			}
   280 			n = len - 2;
   281 		} else {
   282 			n = 0;
   283 			do {
   284 				if(n >= buflen - 1) {
   285 					if(buflen == 0)
   286 						buflen = 16;
   287 					buflen *= 2;
   288 					linebufnew = (char *)realloc(linebuf, buflen);
   289 					if(!linebufnew) {
   290 						free(linebuf);
   291 						error = "Out of memory";
   292 						return NULL;
   293 					}
   294 					linebuf = linebufnew;
   295 				}
   296 				if(SDL_RWread(src, linebuf + n, 1, 1) <= 0) {
   297 					error = "Premature end of data";
   298 					return NULL;
   299 				}
   300 			} while(linebuf[n++] != '"');
   301 			n--;
   302 		}
   303 		linebuf[n] = '\0';
   304 		return linebuf;
   305 	}
   306 }
   307 
   308 #define SKIPSPACE(p)				\
   309 do {						\
   310 	while(isspace((unsigned char)*(p)))	\
   311 	      ++(p);				\
   312 } while(0)
   313 
   314 #define SKIPNONSPACE(p)					\
   315 do {							\
   316 	while(!isspace((unsigned char)*(p)) && *p)	\
   317 	      ++(p);					\
   318 } while(0)
   319 
   320 /* read XPM from either array or RWops */
   321 static SDL_Surface *load_xpm(char **xpm, SDL_RWops *src)
   322 {
   323 	int start = 0;
   324 	SDL_Surface *image = NULL;
   325 	int index;
   326 	int x, y;
   327 	int w, h, ncolors, cpp;
   328 	int indexed;
   329 	Uint8 *dst;
   330 	struct color_hash *colors = NULL;
   331 	SDL_Color *im_colors = NULL;
   332 	char *keystrings = NULL, *nextkey;
   333 	char *line;
   334 	char ***xpmlines = NULL;
   335 	int pixels_len;
   336 
   337 	error = NULL;
   338 	linebuf = NULL;
   339 	buflen = 0;
   340 
   341 	if ( src ) 
   342 		start = SDL_RWtell(src);
   343 
   344 	if(xpm)
   345 		xpmlines = &xpm;
   346 
   347 	line = get_next_line(xpmlines, src, 0);
   348 	if(!line)
   349 		goto done;
   350 	/*
   351 	 * The header string of an XPMv3 image has the format
   352 	 *
   353 	 * <width> <height> <ncolors> <cpp> [ <hotspot_x> <hotspot_y> ]
   354 	 *
   355 	 * where the hotspot coords are intended for mouse cursors.
   356 	 * Right now we don't use the hotspots but it should be handled
   357 	 * one day.
   358 	 */
   359 	if(sscanf(line, "%d %d %d %d", &w, &h, &ncolors, &cpp) != 4
   360 	   || w <= 0 || h <= 0 || ncolors <= 0 || cpp <= 0) {
   361 		error = "Invalid format description";
   362 		goto done;
   363 	}
   364 
   365 	keystrings = (char *)malloc(ncolors * cpp);
   366 	if(!keystrings) {
   367 		error = "Out of memory";
   368 		goto done;
   369 	}
   370 	nextkey = keystrings;
   371 
   372 	/* Create the new surface */
   373 	if(ncolors <= 256) {
   374 		indexed = 1;
   375 		image = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 8,
   376 					     0, 0, 0, 0);
   377 		im_colors = image->format->palette->colors;
   378 		image->format->palette->ncolors = ncolors;
   379 	} else {
   380 		indexed = 0;
   381 		image = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32,
   382 					     0xff0000, 0x00ff00, 0x0000ff, 0);
   383 	}
   384 	if(!image) {
   385 		/* Hmm, some SDL error (out of memory?) */
   386 		goto done;
   387 	}
   388 
   389 	/* Read the colors */
   390 	colors = create_colorhash(ncolors);
   391 	if (!colors) {
   392 		error = "Out of memory";
   393 		goto done;
   394 	}
   395 	for(index = 0; index < ncolors; ++index ) {
   396 		char *p;
   397 		line = get_next_line(xpmlines, src, 0);
   398 		if(!line)
   399 			goto done;
   400 
   401 		p = line + cpp + 1;
   402 
   403 		/* parse a colour definition */
   404 		for(;;) {
   405 			char nametype;
   406 			char *colname;
   407 			Uint32 rgb, pixel;
   408 
   409 			SKIPSPACE(p);
   410 			if(!*p) {
   411 				error = "colour parse error";
   412 				goto done;
   413 			}
   414 			nametype = *p;
   415 			SKIPNONSPACE(p);
   416 			SKIPSPACE(p);
   417 			colname = p;
   418 			SKIPNONSPACE(p);
   419 			if(nametype == 's')
   420 				continue;      /* skip symbolic colour names */
   421 
   422 			if(!color_to_rgb(colname, p - colname, &rgb))
   423 				continue;
   424 
   425 			memcpy(nextkey, line, cpp);
   426 			if(indexed) {
   427 				SDL_Color *c = im_colors + index;
   428 				c->r = (Uint8)(rgb >> 16);
   429 				c->g = (Uint8)(rgb >> 8);
   430 				c->b = (Uint8)(rgb);
   431 				pixel = index;
   432 			} else
   433 				pixel = rgb;
   434 			add_colorhash(colors, nextkey, cpp, pixel);
   435 			nextkey += cpp;
   436 			if(rgb == 0xffffffff)
   437 				SDL_SetColorKey(image, SDL_SRCCOLORKEY, pixel);
   438 			break;
   439 		}
   440 	}
   441 
   442 	/* Read the pixels */
   443 	pixels_len = w * cpp;
   444 	dst = (Uint8 *)image->pixels;
   445 	for(y = 0; y < h; y++) {
   446 		line = get_next_line(xpmlines, src, pixels_len);
   447 		if (!line)
   448 			goto done;
   449 		if(indexed) {
   450 			/* optimization for some common cases */
   451 			if(cpp == 1)
   452 				for(x = 0; x < w; x++)
   453 					dst[x] = (Uint8)QUICK_COLORHASH(colors,
   454 								 line + x);
   455 			else
   456 				for(x = 0; x < w; x++)
   457 					dst[x] = (Uint8)get_colorhash(colors,
   458 							       line + x * cpp,
   459 							       cpp);
   460 		} else {
   461 			for (x = 0; x < w; x++)
   462 				((Uint32*)dst)[x] = get_colorhash(colors,
   463 								line + x * cpp,
   464 								  cpp);
   465 		}
   466 		dst += image->pitch;
   467 	}
   468 
   469 done:
   470 	if(error) {
   471 		if ( src )
   472 			SDL_RWseek(src, start, RW_SEEK_SET);
   473 		if ( image ) {
   474 			SDL_FreeSurface(image);
   475 			image = NULL;
   476 		}
   477 		IMG_SetError(error);
   478 	}
   479 	free(keystrings);
   480 	free_colorhash(colors);
   481 	free(linebuf);
   482 	return(image);
   483 }
   484 
   485 /* Load a XPM type image from an RWops datasource */
   486 SDL_Surface *IMG_LoadXPM_RW(SDL_RWops *src)
   487 {
   488 	if ( !src ) {
   489 		/* The error message has been set in SDL_RWFromFile */
   490 		return NULL;
   491 	}
   492 	return load_xpm(NULL, src);
   493 }
   494 
   495 SDL_Surface *IMG_ReadXPMFromArray(char **xpm)
   496 {
   497 	if ( !xpm ) {
   498 		IMG_SetError("array is NULL");
   499 		return NULL;
   500 	}
   501 	return load_xpm(xpm, NULL);
   502 }
   503 
   504 #else  /* not LOAD_XPM */
   505 
   506 /* See if an image is contained in a data source */
   507 int IMG_isXPM(SDL_RWops *src)
   508 {
   509 	return(0);
   510 }
   511 
   512 
   513 /* Load a XPM type image from an SDL datasource */
   514 SDL_Surface *IMG_LoadXPM_RW(SDL_RWops *src)
   515 {
   516 	return(NULL);
   517 }
   518 
   519 SDL_Surface *IMG_ReadXPMFromArray(char **xpm)
   520 {
   521     return NULL;
   522 }
   523 #endif /* not LOAD_XPM */