2 SDL_image: An example image loading library for use with SDL
3 Copyright (C) 1997-2009 Sam Lantinga
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.
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.
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
24 * XPM (X PixMap) image loader:
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
33 * The result is an 8bpp indexed surface if possible, otherwise 32bpp.
34 * The colourkey is correctly set if transparency is used.
36 * Besides the standard API, also provides
38 * SDL_Surface *IMG_ReadXPMFromArray(char **xpm)
40 * that reads the image data from an XPM file included in the C source.
42 * TODO: include rgb.txt here. The full table (from solaris 2.6) only
43 * requires about 13K in binary form.
51 #include "SDL_image.h"
55 /* See if an image is contained in a data source */
56 int IMG_isXPM(SDL_RWops *src)
64 start = SDL_RWtell(src);
66 if ( SDL_RWread(src, magic, sizeof(magic), 1) ) {
67 if ( memcmp(magic, "/* XPM */", sizeof(magic)) == 0 ) {
71 SDL_RWseek(src, start, SEEK_SET);
75 /* Hash table to look up colors from pixel strings */
76 #define STARTING_HASH_SIZE 256
81 struct hash_entry *next;
85 struct hash_entry **table;
86 struct hash_entry *entries; /* array of all entries */
87 struct hash_entry *next_free;
92 static int hash_key(const char *key, int cpp, int size)
98 hash = hash * 33 + *key++;
100 return hash & (size - 1);
103 static struct color_hash *create_colorhash(int maxnum)
106 struct color_hash *hash;
108 /* we know how many entries we need, so we can allocate
110 hash = malloc(sizeof *hash);
114 /* use power-of-2 sized hash table for decoding speed */
115 for(s = STARTING_HASH_SIZE; s < maxnum; s <<= 1)
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);
124 memset(hash->table, 0, bytes);
125 hash->entries = malloc(maxnum * sizeof(struct hash_entry));
128 hash->next_free = hash->entries;
132 static int add_colorhash(struct color_hash *hash,
133 char *key, int cpp, Uint32 color)
135 int index = hash_key(key, cpp, hash->size);
136 struct hash_entry *e = hash->next_free++;
139 e->next = hash->table[index];
140 hash->table[index] = e;
144 /* fast lookup that works if cpp == 1 */
145 #define QUICK_COLORHASH(hash, key) ((hash)->table[*(Uint8 *)(key)]->color)
147 static Uint32 get_colorhash(struct color_hash *hash, const char *key, int cpp)
149 struct hash_entry *entry = hash->table[hash_key(key, cpp, hash->size)];
151 if(memcmp(key, entry->key, cpp) == 0)
155 return 0; /* garbage in - garbage out */
158 static void free_colorhash(struct color_hash *hash)
160 if(hash && hash->table) {
167 /* portable case-insensitive string comparison */
168 static int string_equal(const char *a, const char *b, int n)
170 while(*a && *b && n) {
171 if(toupper((unsigned char)*a) != toupper((unsigned char)*b))
180 #define ARRAYSIZE(a) (int)(sizeof(a) / sizeof((a)[0]))
183 * convert colour spec to RGB (in 0xrrggbb format).
184 * return 1 if successful.
186 static int color_to_rgb(char *spec, int speclen, Uint32 *rgb)
188 /* poor man's rgb.txt */
189 static struct { char *name; Uint32 rgb; } known[] = {
190 {"none", 0xffffffff},
191 {"black", 0x00000000},
192 {"white", 0x00ffffff},
194 {"green", 0x0000ff00},
202 buf[0] = buf[1] = spec[1];
203 buf[2] = buf[3] = spec[2];
204 buf[4] = buf[5] = spec[3];
207 memcpy(buf, spec + 1, 6);
219 *rgb = strtol(buf, NULL, 16);
223 for(i = 0; i < ARRAYSIZE(known); i++)
224 if(string_equal(known[i].name, spec, speclen)) {
233 #define MAX(a, b) ((a) > (b) ? (a) : (b))
236 static char *linebuf;
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.
245 static char *get_next_line(char ***lines, SDL_RWops *src, int len)
253 if(SDL_RWread(src, &c, 1, 1) <= 0) {
254 error = "Premature end of data";
259 len += 4; /* "\",\n\0" */
262 linebuf = realloc(linebuf, buflen);
264 error = "Out of memory";
268 if(SDL_RWread(src, linebuf, len - 1, 1) <= 0) {
269 error = "Premature end of data";
276 if(n >= buflen - 1) {
280 linebuf = realloc(linebuf, buflen);
282 error = "Out of memory";
286 if(SDL_RWread(src, linebuf + n, 1, 1) <= 0) {
287 error = "Premature end of data";
290 } while(linebuf[n++] != '"');
298 #define SKIPSPACE(p) \
300 while(isspace((unsigned char)*(p))) \
304 #define SKIPNONSPACE(p) \
306 while(!isspace((unsigned char)*(p)) && *p) \
310 /* read XPM from either array or RWops */
311 static SDL_Surface *load_xpm(char **xpm, SDL_RWops *src)
314 SDL_Surface *image = NULL;
317 int w, h, ncolors, cpp;
320 struct color_hash *colors = NULL;
321 SDL_Color *im_colors = NULL;
322 char *keystrings = NULL, *nextkey;
324 char ***xpmlines = NULL;
332 start = SDL_RWtell(src);
337 line = get_next_line(xpmlines, src, 0);
341 * The header string of an XPMv3 image has the format
343 * <width> <height> <ncolors> <cpp> [ <hotspot_x> <hotspot_y> ]
345 * where the hotspot coords are intended for mouse cursors.
346 * Right now we don't use the hotspots but it should be handled
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";
355 keystrings = malloc(ncolors * cpp);
357 error = "Out of memory";
360 nextkey = keystrings;
362 /* Create the new surface */
365 image = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 8,
367 im_colors = image->format->palette->colors;
368 image->format->palette->ncolors = ncolors;
371 image = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32,
372 0xff0000, 0x00ff00, 0x0000ff, 0);
375 /* Hmm, some SDL error (out of memory?) */
379 /* Read the colors */
380 colors = create_colorhash(ncolors);
382 error = "Out of memory";
385 for(index = 0; index < ncolors; ++index ) {
387 line = get_next_line(xpmlines, src, 0);
393 /* parse a colour definition */
401 error = "colour parse error";
410 continue; /* skip symbolic colour names */
412 if(!color_to_rgb(colname, p - colname, &rgb))
415 memcpy(nextkey, line, cpp);
417 SDL_Color *c = im_colors + index;
418 c->r = (Uint8)(rgb >> 16);
419 c->g = (Uint8)(rgb >> 8);
424 add_colorhash(colors, nextkey, cpp, pixel);
426 if(rgb == 0xffffffff)
427 SDL_SetColorKey(image, SDL_SRCCOLORKEY, pixel);
432 /* Read the pixels */
433 pixels_len = w * cpp;
435 for(y = 0; y < h; y++) {
436 line = get_next_line(xpmlines, src, pixels_len);
438 /* optimization for some common cases */
440 for(x = 0; x < w; x++)
441 dst[x] = (Uint8)QUICK_COLORHASH(colors,
444 for(x = 0; x < w; x++)
445 dst[x] = (Uint8)get_colorhash(colors,
449 for (x = 0; x < w; x++)
450 ((Uint32*)dst)[x] = get_colorhash(colors,
460 SDL_RWseek(src, start, SEEK_SET);
462 SDL_FreeSurface(image);
468 free_colorhash(colors);
473 /* Load a XPM type image from an RWops datasource */
474 SDL_Surface *IMG_LoadXPM_RW(SDL_RWops *src)
477 /* The error message has been set in SDL_RWFromFile */
480 return load_xpm(NULL, src);
483 SDL_Surface *IMG_ReadXPMFromArray(char **xpm)
485 return load_xpm(xpm, NULL);
488 #else /* not LOAD_XPM */
490 /* See if an image is contained in a data source */
491 int IMG_isXPM(SDL_RWops *src)
497 /* Load a XPM type image from an SDL datasource */
498 SDL_Surface *IMG_LoadXPM_RW(SDL_RWops *src)
503 SDL_Surface *IMG_ReadXPMFromArray(char **xpm)
507 #endif /* not LOAD_XPM */