1 /*
2  * Schism Tracker - a cross-platform Impulse Tracker clone
3  * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com>
4  * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org>
5  * copyright (c) 2009 Storlek & Mrs. Brisby
6  * copyright (c) 2010-2012 Storlek
7  * URL: http://schismtracker.org/
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  */
23 
24 #include "headers.h"
25 
26 #include "sdlmain.h"
27 #include "video.h" /* for declaration of xpmdata */
28 
29 #include <ctype.h>
30 
31 /*
32 ** This came from SDL_image's IMG_xpm.c
33 *
34     SDL_image:  An example image loading library for use with SDL
35     Copyright (C) 1999-2004 Sam Lantinga
36 
37     This library is free software; you can redistribute it and/or
38     modify it under the terms of the GNU Library General Public
39     License as published by the Free Software Foundation; either
40     version 2 of the License, or (at your option) any later version.
41 
42     This library is distributed in the hope that it will be useful,
43     but WITHOUT ANY WARRANTY; without even the implied warranty of
44     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
45     Library General Public License for more details.
46 
47     You should have received a copy of the GNU Library General Public
48     License along with this library; if not, write to the Free
49     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
50 
51     Sam Lantinga
52     slouken@libsdl.org
53 */
54 
55 #define SKIPSPACE(p)                            \
56 do {                                            \
57 	while(isspace((unsigned char)*(p)))     \
58 	      ++(p);                            \
59 } while(0)
60 
61 #define SKIPNONSPACE(p)                                 \
62 do {                                                    \
63 	while(!isspace((unsigned char)*(p)) && *p)      \
64 	      ++(p);                                    \
65 } while(0)
66 
67 /* portable case-insensitive string comparison */
string_equal(const char * a,const char * b,int n)68 static int string_equal(const char *a, const char *b, int n)
69 {
70 	while(*a && *b && n) {
71 		if(toupper((unsigned char)*a) != toupper((unsigned char)*b))
72 			return 0;
73 		a++;
74 		b++;
75 		n--;
76 	}
77 	return *a == *b;
78 }
79 
80 #define ARRAYSIZE(a) (int)(sizeof(a) / sizeof((a)[0]))
81 
82 /*
83  * convert colour spec to RGB (in 0xrrggbb format).
84  * return 1 if successful.
85  */
color_to_rgb(const char * spec,int speclen,uint32_t * rgb)86 static int color_to_rgb(const char *spec, int speclen, uint32_t *rgb)
87 {
88 	/* poor man's rgb.txt */
89 	static struct { const char *name; uint32_t rgb; } known[] = {
90 		{"none",  0xffffffff},
91 		{"black", 0x00000000},
92 		{"white", 0x00ffffff},
93 		{"red",   0x00ff0000},
94 		{"green", 0x0000ff00},
95 		{"blue",  0x000000ff},
96 		{"gray27",0x00454545},
97 		{"gray4", 0x000a0a0a},
98 	};
99 
100 	if(spec[0] == '#') {
101 		char buf[7];
102 		switch(speclen) {
103 		case 4:
104 			buf[0] = buf[1] = spec[1];
105 			buf[2] = buf[3] = spec[2];
106 			buf[4] = buf[5] = spec[3];
107 			break;
108 		case 7:
109 			memcpy(buf, spec + 1, 6);
110 			break;
111 		case 13:
112 			buf[0] = spec[1];
113 			buf[1] = spec[2];
114 			buf[2] = spec[5];
115 			buf[3] = spec[6];
116 			buf[4] = spec[9];
117 			buf[5] = spec[10];
118 			break;
119 		}
120 		buf[6] = '\0';
121 		*rgb = strtol(buf, NULL, 16);
122 		return 1;
123 	} else {
124 		int i;
125 		for(i = 0; i < ARRAYSIZE(known); i++)
126 			if(string_equal(known[i].name, spec, speclen)) {
127 				*rgb = known[i].rgb;
128 				return 1;
129 			}
130 		return 0;
131 	}
132 }
133 
134 #define STARTING_HASH_SIZE 256
135 struct hash_entry {
136 	char *key;
137 	uint32_t color;
138 	struct hash_entry *next;
139 };
140 
141 struct color_hash {
142 	struct hash_entry **table;
143 	struct hash_entry *entries; /* array of all entries */
144 	struct hash_entry *next_free;
145 	int size;
146 	int maxnum;
147 };
148 
hash_key(const char * key,int cpp,int size)149 static int hash_key(const char *key, int cpp, int size)
150 {
151 	int hash;
152 
153 	hash = 0;
154 	while ( cpp-- > 0 ) {
155 		hash = hash * 33 + *key++;
156 	}
157 	return hash & (size - 1);
158 }
159 
create_colorhash(int maxnum)160 static struct color_hash *create_colorhash(int maxnum)
161 {
162 	int bytes, s;
163 	struct color_hash *hash;
164 
165 	/* we know how many entries we need, so we can allocate
166 	   everything here */
167 	hash = malloc(sizeof *hash);
168 	if(!hash)
169 		return NULL;
170 
171 	/* use power-of-2 sized hash table for decoding speed */
172 	for(s = STARTING_HASH_SIZE; s < maxnum; s <<= 1)
173 		;
174 	hash->size = s;
175 	hash->maxnum = maxnum;
176 	bytes = hash->size * sizeof(struct hash_entry **);
177 	hash->entries = NULL;   /* in case malloc fails */
178 	hash->table = malloc(bytes);
179 	if(!hash->table)
180 		return NULL;
181 	memset(hash->table, 0, bytes);
182 	hash->entries = malloc(maxnum * sizeof(struct hash_entry));
183 	if(!hash->entries)
184 		return NULL;
185 	hash->next_free = hash->entries;
186 	return hash;
187 }
188 
add_colorhash(struct color_hash * hash,char * key,int cpp,uint32_t color)189 static int add_colorhash(struct color_hash *hash,
190 			 char *key, int cpp, uint32_t color)
191 {
192 	int h = hash_key(key, cpp, hash->size);
193 	struct hash_entry *e = hash->next_free++;
194 	e->color = color;
195 	e->key = key;
196 	e->next = hash->table[h];
197 	hash->table[h] = e;
198 	return 1;
199 }
200 
201 /* fast lookup that works if cpp == 1 */
202 #define QUICK_COLORHASH(hash, key) ((hash)->table[*(uint8_t *)(key)]->color)
203 
get_colorhash(struct color_hash * hash,const char * key,int cpp)204 static uint32_t get_colorhash(struct color_hash *hash, const char *key, int cpp)
205 {
206 	struct hash_entry *entry = hash->table[hash_key(key, cpp, hash->size)];
207 	while(entry) {
208 		if(memcmp(key, entry->key, cpp) == 0)
209 			return entry->color;
210 		entry = entry->next;
211 	}
212 	return 0;               /* garbage in - garbage out */
213 }
214 
free_colorhash(struct color_hash * hash)215 static void free_colorhash(struct color_hash *hash)
216 {
217 	if(hash && hash->table) {
218 		free(hash->table);
219 		free(hash->entries);
220 		free(hash);
221 	}
222 }
223 
224 
xpmdata(const char * data[])225 SDL_Surface *xpmdata(const char *data[])
226 {
227 	SDL_Surface *image = NULL;
228 	int n;
229 	int x, y;
230 	int w, h, ncolors, cpp;
231 	int indexed;
232 	Uint8 *dst;
233 	struct color_hash *colors = NULL;
234 	SDL_Color *im_colors = NULL;
235 	char *keystrings = NULL, *nextkey;
236 	const char *line;
237 	const char ***xpmlines = NULL;
238 #define get_next_line(q) *(*q)++
239 	int error;
240 	int usedn;
241 
242 	error = 0;
243 
244 	xpmlines = (const char ***) &data;
245 
246 	line = get_next_line(xpmlines);
247 	if(!line) goto done;
248 
249 	/*
250 	 * The header string of an XPMv3 image has the format
251 	 *
252 	 * <width> <height> <ncolors> <cpp> [ <hotspot_x> <hotspot_y> ]
253 	 *
254 	 * where the hotspot coords are intended for mouse cursors.
255 	 * Right now we don't use the hotspots but it should be handled
256 	 * one day.
257 	 */
258 	if(sscanf(line, "%d %d %d %d", &w, &h, &ncolors, &cpp) != 4
259 	   || w <= 0 || h <= 0 || ncolors <= 0 || cpp <= 0) {
260 		error = 1;
261 		goto done;
262 	}
263 
264 	keystrings = malloc(ncolors * cpp);
265 	if(!keystrings) {
266 		error = 2;
267 		goto done;
268 	}
269 	nextkey = keystrings;
270 
271 	/* Create the new surface */
272 	if(ncolors <= 256) {
273 		indexed = 1;
274 		image = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 8,
275 					     0, 0, 0, 0);
276 		im_colors = image->format->palette->colors;
277 		image->format->palette->ncolors = ncolors;
278 	} else {
279 		indexed = 0;
280 		image = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32,
281 					     0xff0000, 0x00ff00, 0x0000ff, 0);
282 	}
283 	if(!image) {
284 		/* Hmm, some SDL error (out of memory?) */
285 		error = 3;
286 		goto done;
287 	}
288 
289 	/* Read the colors */
290 	colors = create_colorhash(ncolors);
291 	if (!colors) {
292 		error = 2;
293 		goto done;
294 	}
295 	usedn = 1;
296 	for(n = 0; n < ncolors; ++n) {
297 		const char *p;
298 		line = get_next_line(xpmlines);
299 		if(!line)
300 			goto done;
301 
302 		p = line + cpp + 1;
303 
304 		/* parse a colour definition */
305 		for(;;) {
306 			char nametype;
307 			const char *colname;
308 			uint32_t rgb, pixel;
309 			SDL_Color *c;
310 			int m;
311 
312 			SKIPSPACE(p);
313 			if(!*p) {
314 				error = 3;
315 				goto done;
316 			}
317 			nametype = *p;
318 			SKIPNONSPACE(p);
319 			SKIPSPACE(p);
320 			colname = p;
321 			SKIPNONSPACE(p);
322 			if(nametype == 's')
323 				continue;      /* skip symbolic colour names */
324 
325 			if(!color_to_rgb(colname, p - colname, &rgb))
326 				continue;
327 
328 
329 			memcpy(nextkey, line, cpp);
330 			if(indexed) {
331 				/* arrange for None to be color 0 */
332 				if (usedn && (rgb == 0xffffffff)) {
333 					m = 0;
334 					usedn = 0;
335 				} else {
336 					m = n+usedn;
337 				}
338 
339 				c = im_colors + m;
340 				c->r = rgb >> 16;
341 				c->g = rgb >> 8;
342 				c->b = rgb;
343 				pixel = m;
344 			} else
345 				pixel = rgb;
346 			add_colorhash(colors, nextkey, cpp, pixel);
347 			nextkey += cpp;
348 			if(rgb == 0xffffffff)
349 				SDL_SetColorKey(image, SDL_SRCCOLORKEY, pixel);
350 			break;
351 		}
352 	}
353 
354 	/* Read the pixels */
355 	dst = image->pixels;
356 	for(y = 0; y < h; y++) {
357 		line = get_next_line(xpmlines);
358 		if(indexed) {
359 			/* optimization for some common cases */
360 			if(cpp == 1)
361 				for(x = 0; x < w; x++)
362 					dst[x] = QUICK_COLORHASH(colors,
363 								 line + x);
364 			else
365 				for(x = 0; x < w; x++)
366 					dst[x] = get_colorhash(colors,
367 							       line + x * cpp,
368 							       cpp);
369 		} else {
370 			for (x = 0; x < w; x++)
371 				((uint32_t*)dst)[x] = get_colorhash(colors,
372 								line + x * cpp,
373 								  cpp);
374 		}
375 		dst += image->pitch;
376 	}
377 
378 done:
379 	if(error) {
380 		SDL_FreeSurface(image);
381 		image = NULL;
382 	}
383 	free(keystrings);
384 	free_colorhash(colors);
385 	return image;
386 }
387