SRB2/src/sdl/IMG_xpm.c
2014-03-15 13:11:35 -04:00

506 lines
16 KiB
C

/*
SDL_image: An example image loading library for use with SDL
Copyright (C) 1999-2004 Sam Lantinga
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Sam Lantinga
slouken@libsdl.org
*/
/* $Id: IMG_xpm.c,v 1.10 2004/01/04 22:04:38 slouken Exp $ */
/*
* XPM (X PixMap) image loader:
*
* Supports the XPMv3 format, EXCEPT:
* - hotspot coordinates are ignored
* - only colour ('c') colour symbols are used
* - rgb.txt is not used (for portability), so only RGB colours
* are recognized (#rrggbb etc) - only a few basic colour names are
* handled
*
* The result is an 8bpp indexed surface if possible, otherwise 32bpp.
* The colourkey is correctly set if transparency is used.
*
* Besides the standard API, also provides
*
* SDL_Surface *IMG_ReadXPMFromArray(char **xpm)
*
* that reads the image data from an XPM file included in the C source.
*
* TODO: include rgb.txt here. The full table (from solaris 2.6) only
* requires about 13K in binary form.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
//#include "SDL_image.h"
#ifdef LOAD_XPM
/* See if an image is contained in a data source */
#if 0
int IMG_isXPM(SDL_RWops *src)
{
char magic[9];
return (SDL_RWread(src, magic, sizeof (magic), 1)
&& memcmp(magic, "/* XPM */", 9) == 0);
}
#endif
/* Hash table to look up colors from pixel strings */
#define STARTING_HASH_SIZE 256
struct hash_entry {
char *key;
Uint32 color;
struct hash_entry *next;
};
struct color_hash {
struct hash_entry **table;
struct hash_entry *entries; /* array of all entries */
struct hash_entry *next_free;
size_t size;
int maxnum;
};
static int hash_key(const char *key, int cpp, size_t size)
{
int hash;
hash = 0;
while ( cpp-- > 0 ) {
hash = hash * 33 + *key++;
}
return (int)(hash & (size - 1));
}
static struct color_hash *create_colorhash(int maxnum)
{
size_t bytes;
int s;
struct color_hash *hash;
/* we know how many entries we need, so we can allocate
everything here */
hash = malloc(sizeof *hash);
if (!hash)
return NULL;
/* use power-of-2 sized hash table for decoding speed */
for (s = STARTING_HASH_SIZE; s < maxnum; s <<= 1)
;
hash->size = s;
hash->maxnum = maxnum;
bytes = hash->size * sizeof (struct hash_entry **);
hash->entries = NULL; /* in case malloc fails */
hash->table = malloc(bytes);
if (!hash->table)
return NULL;
memset(hash->table, 0, bytes);
hash->entries = malloc(maxnum * sizeof (struct hash_entry));
if (!hash->entries)
{
free(hash->table);
return NULL;
}
hash->next_free = hash->entries;
return hash;
}
static int add_colorhash(struct color_hash *hash,
char *key, int cpp, Uint32 color)
{
const int indexkey = hash_key(key, cpp, hash->size);
struct hash_entry *e = hash->next_free++;
e->color = color;
e->key = key;
e->next = hash->table[indexkey];
hash->table[indexkey] = e;
return 1;
}
/* fast lookup that works if cpp == 1 */
#define QUICK_COLORHASH(hash, key) ((hash)->table[*(const Uint8 *)(key)]->color)
static Uint32 get_colorhash(struct color_hash *hash, const char *key, int cpp)
{
struct hash_entry *entry = hash->table[hash_key(key, cpp, hash->size)];
while (entry) {
if (memcmp(key, entry->key, cpp) == 0)
return entry->color;
entry = entry->next;
}
return 0; /* garbage in - garbage out */
}
static void free_colorhash(struct color_hash *hash)
{
if (hash && hash->table) {
free(hash->table);
free(hash->entries);
free(hash);
}
}
/* portable case-insensitive string comparison */
static int string_equal(const char *a, const char *b, size_t n)
{
while (*a && *b && n) {
if (toupper((unsigned char)*a) != toupper((unsigned char)*b))
return 0;
a++;
b++;
n--;
}
return *a == *b;
}
#undef ARRAYSIZE
#define ARRAYSIZE(a) (int)(sizeof (a) / sizeof ((a)[0]))
/*
* convert colour spec to RGB (in 0xrrggbb format).
* return 1 if successful.
*/
static int color_to_rgb(const char *spec, size_t speclen, Uint32 *rgb)
{
/* poor man's rgb.txt */
static struct { const char *name; Uint32 rgb; } known[] = {
{"none", 0xffffffff},
{"black", 0x00000000},
{"white", 0x00ffffff},
{"red", 0x00ff0000},
{"green", 0x0000ff00},
{"blue", 0x000000ff}
};
if (spec[0] == '#') {
char buf[7];
switch (speclen) {
case 4:
buf[0] = buf[1] = spec[1];
buf[2] = buf[3] = spec[2];
buf[4] = buf[5] = spec[3];
break;
case 7:
memcpy(buf, spec + 1, 6);
break;
case 13:
buf[0] = spec[1];
buf[1] = spec[2];
buf[2] = spec[5];
buf[3] = spec[6];
buf[4] = spec[9];
buf[5] = spec[10];
break;
}
buf[6] = '\0';
*rgb = (Uint32)strtol(buf, NULL, 16);
return 1;
} else {
int i;
for (i = 0; i < ARRAYSIZE(known); i++)
if (string_equal(known[i].name, spec, speclen)) {
*rgb = known[i].rgb;
return 1;
}
return 0;
}
}
#ifndef MAX
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#endif
static char *linebuf;
static int buflen;
static const char *error;
/*
* Read next line from the source.
* If len > 0, it's assumed to be at least len chars (for efficiency).
* Return NULL and set error upon EOF or parse error.
*/
static const char *get_next_line(const char ***lines, SDL_RWops *src, int len)
{
char *linebufnew;
if (lines) {
return *(*lines)++;
} else {
char c;
int n;
do {
if (SDL_RWread(src, &c, 1, 1) <= 0) {
error = "Premature end of data";
return NULL;
}
} while (c != '"');
if (len) {
len += 4; /* "\",\n\0" */
if (len > buflen){
buflen = len;
linebufnew = realloc(linebuf, buflen);
if(!linebufnew) {
free(linebuf);
error = "Out of memory";
return NULL;
}
linebuf = linebufnew;
}
if (SDL_RWread(src, linebuf, len - 1, 1) <= 0) {
error = "Premature end of data";
return NULL;
}
n = len - 2;
} else {
n = 0;
do {
if (n >= buflen - 1) {
if (buflen == 0)
buflen = 16;
buflen *= 2;
linebufnew = realloc(linebuf, buflen);
if(!linebufnew) {
free(linebuf);
error = "Out of memory";
return NULL;
}
linebuf = linebufnew;
}
if (SDL_RWread(src, linebuf + n, 1, 1) <= 0) {
error = "Premature end of data";
return NULL;
}
} while (linebuf[n++] != '"');
n--;
}
linebuf[n] = '\0';
return linebuf;
}
}
#define SKIPSPACE(p) \
do { \
while (isspace((unsigned char)*(p))) \
++(p); \
} while (0)
#define SKIPNONSPACE(p) \
do { \
while (!isspace((unsigned char)*(p)) && *p) \
++(p); \
} while (0)
/* read XPM from either array or RWops */
static SDL_Surface *load_xpm(const char **xpm, SDL_RWops *src)
{
SDL_Surface *image = NULL;
int indexc;
int x, y;
int w, h, ncolors, cpp;
int indexed;
Uint8 *dst;
struct color_hash *colors = NULL;
SDL_Color *im_colors = NULL;
char *keystrings = NULL, *nextkey;
const char *line;
const char ***xpmlines = NULL;
int pixels_len;
error = NULL;
linebuf = NULL;
buflen = 0;
if (xpm)
xpmlines = &xpm;
line = get_next_line(xpmlines, src, 0);
if (!line)
goto done;
/*
* The header string of an XPMv3 image has the format
*
* <width> <height> <ncolors> <cpp> [ <hotspot_x> <hotspot_y> ]
*
* where the hotspot coords are intended for mouse cursors.
* Right now we don't use the hotspots but it should be handled
* one day.
*/
if (sscanf(line, "%d %d %d %d", &w, &h, &ncolors, &cpp) != 4
|| w <= 0 || h <= 0 || ncolors <= 0 || cpp <= 0) {
error = "Invalid format description";
goto done;
}
keystrings = malloc(ncolors * cpp);
if (!keystrings) {
error = "Out of memory";
goto done;
}
nextkey = keystrings;
/* Create the new surface */
if (ncolors <= 256) {
indexed = 1;
image = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 8,
0, 0, 0, 0);
im_colors = image->format->palette->colors;
image->format->palette->ncolors = ncolors;
} else {
indexed = 0;
image = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32,
0xff0000, 0x00ff00, 0x0000ff, 0);
}
if (!image) {
/* Hmm, some SDL error (out of memory?) */
goto done;
}
/* Read the colors */
colors = create_colorhash(ncolors);
if (!colors) {
error = "Out of memory";
goto done;
}
for (indexc = 0; indexc < ncolors; ++indexc ) {
const char *p;
line = get_next_line(xpmlines, src, 0);
if (!line)
goto done;
p = line + cpp + 1;
/* parse a colour definition */
for (;;) {
char nametype;
const char *colname;
Uint32 rgb, pixel;
SKIPSPACE(p);
if (!*p) {
error = "colour parse error";
goto done;
}
nametype = *p;
SKIPNONSPACE(p);
SKIPSPACE(p);
colname = p;
SKIPNONSPACE(p);
if (nametype == 's')
continue; /* skip symbolic colour names */
if (!color_to_rgb(colname, p - colname, &rgb))
continue;
memcpy(nextkey, line, cpp);
if (indexed) {
SDL_Color *c = im_colors + indexc;
c->r = (Uint8)(rgb >> 16);
c->g = (Uint8)(rgb >> 8);
c->b = (Uint8)(rgb);
pixel = indexc;
} else
pixel = rgb;
add_colorhash(colors, nextkey, cpp, pixel);
nextkey += cpp;
if (rgb == 0xffffffff)
SDL_SetColorKey(image, SDL_SRCCOLORKEY, pixel);
break;
}
}
/* Read the pixels */
pixels_len = w * cpp;
dst = image->pixels;
for (y = 0; y < h; y++) {
line = get_next_line(xpmlines, src, pixels_len);
if (indexed) {
/* optimization for some common cases */
if (cpp == 1)
for (x = 0; x < w; x++)
dst[x] = (Uint8)QUICK_COLORHASH(colors,
line + x);
else
for (x = 0; x < w; x++)
dst[x] = (Uint8)get_colorhash(colors,
line + x * cpp,
cpp);
} else {
for (x = 0; x < w; x++)
((Uint32*)dst)[x] = get_colorhash(colors,
line + x * cpp,
cpp);
}
dst += image->pitch;
}
done:
if (error) {
SDL_FreeSurface(image);
image = NULL;
SDL_SetError(error);
}
free(keystrings);
free_colorhash(colors);
free(linebuf);
return(image);
}
/* Load a XPM type image from an RWops datasource */
#if 0
SDL_Surface *IMG_LoadXPM_RW(SDL_RWops *src)
{
if ( !src ) {
/* The error message has been set in SDL_RWFromFile */
return NULL;
}
return load_xpm(NULL, src);
}
#endif
static inline SDL_Surface *IMG_ReadXPMFromArray(const char **xpm)
{
return load_xpm(xpm, NULL);
}
#else /* not LOAD_XPM */
/* See if an image is contained in a data source */
#if 0
int IMG_isXPM(SDL_RWops *src)
{
return(0);
}
/* Load a XPM type image from an SDL datasource */
SDL_Surface *IMG_LoadXPM_RW(SDL_RWops *src)
{
return(NULL);
}
#endif
static inline SDL_Surface *IMG_ReadXPMFromArray(const char **xpm)
{
return NULL;
}
#endif /* not LOAD_XPM */