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