632 lines
20 KiB
C
632 lines
20 KiB
C
/*
|
|
* static char *rcsid_image_c =
|
|
* "$Id: image.c 11578 2009-02-23 22:02:27Z lalo $";
|
|
*/
|
|
|
|
/*
|
|
CrossFire, A Multiplayer game for X-windows
|
|
|
|
Copyright (C) 2002 Mark Wedel & Crossfire Development Team
|
|
Copyright (C) 1992 Frank Tore Johansen
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program 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 General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
The maintainer of this code can be reached at crossfire-devel@real-time.com
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
* Handles face-related stuff, including the actual face data.
|
|
*/
|
|
|
|
#include <global.h>
|
|
#include <stdio.h>
|
|
#include "image.h"
|
|
|
|
New_Face *new_faces;
|
|
|
|
/**
|
|
* bmappair and ::xbm are used when looking for the image id numbers
|
|
* of a face by name. xbm is sorted alphabetically so that bsearch
|
|
* can be used to quickly find the entry for a name. the number is
|
|
* then an index into the new_faces array.
|
|
* This data is redundant with new_face information - the difference
|
|
* is that this data gets sorted, and that doesn't necessarily happen
|
|
* with the new_face data - when accessing new_face[some number],
|
|
* that some number corresponds to the face at that number - for
|
|
* xbm, it may not. At current time, these do in fact match because
|
|
* the bmaps file is created in a sorted order.
|
|
*/
|
|
struct bmappair {
|
|
char *name;
|
|
unsigned int number;
|
|
};
|
|
/**
|
|
* The xbm array (which contains name and number information, and
|
|
* is then sorted) contains nroffiles entries.
|
|
*/
|
|
static struct bmappair *xbm = NULL;
|
|
|
|
/**
|
|
* Following can just as easily be pointers, but
|
|
* it is easier to keep them like this.
|
|
*/
|
|
New_Face *blank_face, *empty_face, *smooth_face;
|
|
|
|
|
|
/** nroffiles is the actual number of bitmaps defined. */
|
|
static int nroffiles = 0;
|
|
|
|
/** nrofpixmaps is the number of bitmaps loaded. With
|
|
* the automatic generation of the bmaps file, this is now equal
|
|
* to nroffiles.
|
|
*/
|
|
int nrofpixmaps = 0;
|
|
|
|
face_sets facesets[MAX_FACE_SETS]; /**< All facesets */
|
|
|
|
/**
|
|
* The only thing this table is used for now is to
|
|
* translate the colorname in the magicmap field of the
|
|
* face into a numeric index that is then sent to the
|
|
* client for magic map commands. The order of this table
|
|
* must match that of the NDI colors in include/newclient.h.
|
|
*/
|
|
static const char *const colorname[] = {
|
|
"black", /* 0 */
|
|
"white", /* 1 */
|
|
"blue", /* 2 */
|
|
"red", /* 3 */
|
|
"orange", /* 4 */
|
|
"light_blue", /* 5 */
|
|
"dark_orange", /* 6 */
|
|
"green", /* 7 */
|
|
"light_green", /* 8 */
|
|
"grey", /* 9 */
|
|
"brown", /* 10 */
|
|
"yellow", /* 11 */
|
|
"khaki" /* 12 */
|
|
};
|
|
|
|
/**
|
|
* Used for bsearch searching.
|
|
*/
|
|
static int compar(const struct bmappair *a, const struct bmappair *b) {
|
|
return strcmp(a->name, b->name);
|
|
}
|
|
|
|
/**
|
|
* Finds a color by name.
|
|
*
|
|
* @param name
|
|
* color name, case-sensitive.
|
|
* @return
|
|
* the matching color in the coloralias if found,
|
|
* 0 otherwise.
|
|
*
|
|
* @note
|
|
* 0 will actually be black, so there is no
|
|
* way the calling function can tell if an error occurred or not
|
|
*/
|
|
static uint8 find_color(const char *name) {
|
|
uint8 i;
|
|
|
|
for (i = 0; i < sizeof(colorname)/sizeof(*colorname); i++)
|
|
if (!strcmp(name, colorname[i]))
|
|
return i;
|
|
|
|
LOG(llevError, "Unknown color: %s\n", name);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* This reads the lib/faces file, getting color and visibility information.
|
|
* it is called by read_bmap_names().
|
|
*
|
|
* @note
|
|
* will call exit() if file doesn't exist.
|
|
*/
|
|
static void read_face_data(void) {
|
|
char buf[MAX_BUF], *cp;
|
|
New_Face *on_face = NULL;
|
|
FILE *fp;
|
|
|
|
snprintf(buf, sizeof(buf), "%s/faces", settings.datadir);
|
|
LOG(llevDebug, "Reading faces from %s...\n", buf);
|
|
if ((fp = fopen(buf, "r")) == NULL) {
|
|
LOG(llevError, "Cannot open faces file: %s\n", strerror_local(errno, buf, sizeof(buf)));
|
|
exit(-1);
|
|
}
|
|
|
|
while (fgets(buf, MAX_BUF, fp) != NULL) {
|
|
if (*buf == '#')
|
|
continue;
|
|
if (!strncmp(buf, "end", 3)) {
|
|
on_face = NULL;
|
|
} else if (!strncmp(buf, "face", 4)) {
|
|
unsigned tmp;
|
|
|
|
cp = buf+5;
|
|
cp[strlen(cp)-1] = '\0'; /* remove newline */
|
|
|
|
if ((tmp = find_face(cp, (unsigned)-1)) == (unsigned)-1) {
|
|
LOG(llevError, "Could not find face %s\n", cp);
|
|
continue;
|
|
}
|
|
on_face = &new_faces[tmp];
|
|
on_face->visibility = 0;
|
|
} else if (on_face == NULL) {
|
|
LOG(llevError, "Got line with no face set: %s\n", buf);
|
|
} else if (!strncmp(buf, "visibility", 10)) {
|
|
on_face->visibility = atoi(buf+11);
|
|
} else if (!strncmp(buf, "magicmap", 8)) {
|
|
cp = buf+9;
|
|
cp[strlen(cp)-1] = '\0';
|
|
on_face->magicmap = find_color(cp);
|
|
} else if (!strncmp(buf, "is_floor", 8)) {
|
|
int value = atoi(buf+9);
|
|
if (value)
|
|
on_face->magicmap |= FACE_FLOOR;
|
|
} else
|
|
LOG(llevDebug, "Got unknown line in faces file: %s\n", buf);
|
|
}
|
|
LOG(llevDebug, "done\n");
|
|
fclose(fp);
|
|
}
|
|
|
|
/**
|
|
* This reads the bmaps file to get all the bitmap names and
|
|
* stuff. It only needs to be done once, because it is player
|
|
* independent (ie, what display the person is on will not make a
|
|
* difference.)
|
|
*
|
|
* @note
|
|
* will call exit() if file doesn't exist, and abort() in case of memory error.
|
|
*/
|
|
void read_bmap_names(void) {
|
|
char buf[MAX_BUF], *p, *q;
|
|
FILE *fp;
|
|
int value, nrofbmaps = 0, i;
|
|
size_t l;
|
|
|
|
bmaps_checksum = 0;
|
|
snprintf(buf, sizeof(buf), "%s/bmaps", settings.datadir);
|
|
LOG(llevDebug, "Reading bmaps from %s...\n", buf);
|
|
if ((fp = fopen(buf, "r")) == NULL) {
|
|
LOG(llevError, "Cannot open bmaps file: %s\n", strerror_local(errno, buf, sizeof(buf)));
|
|
exit(-1);
|
|
}
|
|
|
|
/* First count how many bitmaps we have, so we can allocate correctly */
|
|
while (fgets(buf, MAX_BUF, fp) != NULL)
|
|
if (buf[0] != '#' && buf[0] != '\n')
|
|
nrofbmaps++;
|
|
rewind(fp);
|
|
|
|
xbm = (struct bmappair *)malloc(sizeof(struct bmappair)*nrofbmaps);
|
|
if (xbm == NULL) {
|
|
LOG(llevError, "read_bmap_names: xbm memory allocation failure.\n");
|
|
abort();
|
|
}
|
|
memset(xbm, 0, sizeof(struct bmappair)*nrofbmaps);
|
|
|
|
nroffiles = 0;
|
|
while (nroffiles < nrofbmaps && fgets(buf, MAX_BUF, fp) != NULL) {
|
|
if (*buf == '#')
|
|
continue;
|
|
|
|
p = (*buf == '\\') ? (buf+1) : buf;
|
|
if (!(p = strtok(p, " \t")) || !(q = strtok(NULL, " \t\n"))) {
|
|
LOG(llevDebug, "Warning, syntax error: %s\n", buf);
|
|
continue;
|
|
}
|
|
value = atoi(p);
|
|
xbm[nroffiles].name = strdup_local(q);
|
|
|
|
/* We need to calculate the checksum of the bmaps file
|
|
* name->number mapping to send to the client. This does not
|
|
* need to match what sum or other utility may come up with -
|
|
* as long as we get the same results on the same real file
|
|
* data, it does the job as it lets the client know if
|
|
* the file has the same data or not.
|
|
*/
|
|
ROTATE_RIGHT(bmaps_checksum);
|
|
bmaps_checksum += value&0xff;
|
|
bmaps_checksum &= 0xffffffff;
|
|
|
|
ROTATE_RIGHT(bmaps_checksum);
|
|
bmaps_checksum += (value>>8)&0xff;
|
|
bmaps_checksum &= 0xffffffff;
|
|
for (l = 0; l < strlen(q); l++) {
|
|
ROTATE_RIGHT(bmaps_checksum);
|
|
bmaps_checksum += q[l];
|
|
bmaps_checksum &= 0xffffffff;
|
|
}
|
|
|
|
xbm[nroffiles].number = value;
|
|
nroffiles++;
|
|
if (value >= nrofpixmaps)
|
|
nrofpixmaps = value+1;
|
|
}
|
|
fclose(fp);
|
|
|
|
LOG(llevDebug, "done (got %d/%d/%d)\n", nrofpixmaps, nrofbmaps, nroffiles);
|
|
|
|
new_faces = (New_Face *)malloc(sizeof(New_Face)*nrofpixmaps);
|
|
if (new_faces == NULL) {
|
|
LOG(llevError, "read_bmap_names: new_faces memory allocation failure.\n");
|
|
abort();
|
|
}
|
|
|
|
for (i = 0; i < nrofpixmaps; i++) {
|
|
new_faces[i].name = "";
|
|
new_faces[i].number = i;
|
|
new_faces[i].visibility = 0;
|
|
new_faces[i].magicmap = 255;
|
|
new_faces[i].smoothface = (uint16)-1;
|
|
}
|
|
|
|
for (i = 0; i < nroffiles; i++) {
|
|
new_faces[xbm[i].number].name = xbm[i].name;
|
|
}
|
|
|
|
qsort(xbm, nroffiles, sizeof(struct bmappair), (int (*)(const void *, const void *))compar);
|
|
|
|
read_face_data();
|
|
|
|
for (i = 0; i < nrofpixmaps; i++) {
|
|
if (new_faces[i].magicmap == 255) {
|
|
new_faces[i].magicmap = 0;
|
|
}
|
|
}
|
|
/* Actually forcefully setting the colors here probably should not
|
|
* be done - it could easily create confusion.
|
|
*/
|
|
blank_face = &new_faces[find_face(BLANK_FACE_NAME, 0)];
|
|
blank_face->magicmap = find_color("khaki")|FACE_FLOOR;
|
|
|
|
empty_face = &new_faces[find_face(EMPTY_FACE_NAME, 0)];
|
|
|
|
smooth_face = &new_faces[find_face(SMOOTH_FACE_NAME, 0)];
|
|
}
|
|
|
|
/**
|
|
* This returns an the face number of face 'name'. Number is constant
|
|
* during an invocation, but not necessarily between versions (this
|
|
* is because the faces are arranged in alphabetical order, so
|
|
* if a face is removed or added, all faces after that will now
|
|
* have a different number.
|
|
*
|
|
* @param name
|
|
* face to search for
|
|
* @param error
|
|
* value to return if face was not found.
|
|
*
|
|
* @note
|
|
* If a face is not found, then error is returned. This can be useful if
|
|
* you want some default face used, or can be set to negative
|
|
* so that it will be known that the face could not be found
|
|
* (needed in client, so that it will know to request that image
|
|
* from the server)
|
|
*/
|
|
unsigned find_face(const char *name, unsigned error) {
|
|
struct bmappair *bp, tmp;
|
|
char *p;
|
|
|
|
if ((p = strchr(name, '\n')))
|
|
*p = '\0';
|
|
|
|
tmp.name = (char *)name;
|
|
bp = (struct bmappair *)bsearch(&tmp, xbm, nroffiles, sizeof(struct bmappair), (int (*)(const void *, const void *))compar);
|
|
|
|
return bp ? bp->number : error;
|
|
}
|
|
|
|
/**
|
|
* Reads the smooth file to know how to smooth datas.
|
|
* the smooth file if made of 2 elements lines.
|
|
* lines starting with # are comment
|
|
* the first element of line is face to smooth
|
|
* the next element is the 16x2 faces picture
|
|
* used for smoothing
|
|
*
|
|
* @note
|
|
* will call exit() if file can't be opened.
|
|
*/
|
|
int read_smooth(void) {
|
|
char buf[MAX_BUF], *p, *q;
|
|
FILE *fp;
|
|
int regular, smoothed, nrofsmooth = 0;
|
|
|
|
snprintf(buf, sizeof(buf), "%s/smooth", settings.datadir);
|
|
LOG(llevDebug, "Reading smooth from %s...\n", buf);
|
|
if ((fp = fopen(buf, "r")) == NULL) {
|
|
LOG(llevError, "Cannot open smooth file %s: %s\n", strerror_local(errno, buf, sizeof(buf)));
|
|
exit(-1);
|
|
}
|
|
|
|
while (fgets(buf, MAX_BUF, fp) != NULL) {
|
|
if (*buf == '#')
|
|
continue;
|
|
|
|
p = strchr(buf, ' ');
|
|
if (!p)
|
|
continue;
|
|
|
|
*p = '\0';
|
|
q = buf;
|
|
regular = find_face(q, (unsigned)-1);
|
|
if (regular == (unsigned)-1) {
|
|
LOG(llevError, "invalid regular face: %s\n", q);
|
|
continue;
|
|
}
|
|
q = p+1;
|
|
smoothed = find_face(q, (unsigned)-1);
|
|
if (smoothed == (unsigned)-1) {
|
|
LOG(llevError, "invalid smoothed face: %s\n", q);
|
|
continue;
|
|
}
|
|
|
|
new_faces[regular].smoothface = smoothed;
|
|
|
|
nrofsmooth++;
|
|
}
|
|
fclose(fp);
|
|
|
|
LOG(llevDebug, "done (got %d smooth entries)\n", nrofsmooth);
|
|
return nrofsmooth;
|
|
}
|
|
|
|
/**
|
|
* Find the smooth face for a given face.
|
|
*
|
|
* @param face the face to find the smoothing face for
|
|
*
|
|
* @param smoothed return value: set to smooth face
|
|
*
|
|
* @return 1=smooth face found, 0=no smooth face found
|
|
*/
|
|
int find_smooth(uint16 face, uint16 *smoothed) {
|
|
(*smoothed) = 0;
|
|
|
|
if (face < nrofpixmaps) {
|
|
if (new_faces[face].smoothface == ((uint16)-1))
|
|
return 0;
|
|
|
|
(*smoothed) = new_faces[face].smoothface;
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Deallocates memory allocated by read_bmap_names() and read_smooth().
|
|
*/
|
|
void free_all_images(void) {
|
|
int i;
|
|
|
|
for (i = 0; i < nroffiles; i++)
|
|
free(xbm[i].name);
|
|
free(xbm);
|
|
free(new_faces);
|
|
}
|
|
|
|
/**
|
|
* Checks fallback are correctly defined.
|
|
* This is a simple recursive function that makes sure the fallbacks
|
|
* are all proper (eg, the fall back to defined sets, and also
|
|
* eventually fall back to 0). At the top level, togo is set to
|
|
* MAX_FACE_SETS. If togo gets to zero, it means we have a loop.
|
|
* This is only run when we first load the facesets.
|
|
*/
|
|
static void check_faceset_fallback(int faceset, int togo) {
|
|
int fallback = facesets[faceset].fallback;
|
|
|
|
/* proper case - falls back to base set */
|
|
if (fallback == 0)
|
|
return;
|
|
|
|
if (!facesets[fallback].prefix) {
|
|
LOG(llevError, "Face set %d falls to non set faceset %d\n", faceset, fallback);
|
|
abort();
|
|
}
|
|
togo--;
|
|
if (togo == 0) {
|
|
LOG(llevError, "Infinite loop found in facesets. aborting.\n");
|
|
abort();
|
|
}
|
|
check_faceset_fallback(fallback, togo);
|
|
}
|
|
|
|
/**
|
|
* Loads all the image types into memory.
|
|
*
|
|
* This way, we can easily send them to the client. We should really
|
|
* do something better than abort on any errors - on the other hand,
|
|
* these are all fatal to the server (can't work around them), but the
|
|
* abort just seems a bit messy (exit would probably be better.)
|
|
*
|
|
* Couple of notes: We assume that the faces are in a continous block.
|
|
* This works fine for now, but this could perhaps change in the future
|
|
*
|
|
* Function largely rewritten May 2000 to be more general purpose.
|
|
* The server itself does not care what the image data is - to the server,
|
|
* it is just data it needs to allocate. As such, the code is written
|
|
* to do such.
|
|
*/
|
|
|
|
void read_client_images(void) {
|
|
char filename[400];
|
|
char buf[HUGE_BUF];
|
|
char *cp, *cps[7];
|
|
FILE *infile;
|
|
int num, len, compressed, fileno, i, badline;
|
|
|
|
memset(facesets, 0, sizeof(facesets));
|
|
snprintf(filename, sizeof(filename), "%s/image_info", settings.datadir);
|
|
if ((infile = open_and_uncompress(filename, 0, &compressed)) == NULL) {
|
|
LOG(llevError, "Unable to open %s\n", filename);
|
|
abort();
|
|
}
|
|
while (fgets(buf, HUGE_BUF-1, infile) != NULL) {
|
|
badline = 0;
|
|
|
|
if (buf[0] == '#')
|
|
continue;
|
|
if (!(cps[0] = strtok(buf, ":")))
|
|
badline = 1;
|
|
for (i = 1; i < 7; i++) {
|
|
if (!(cps[i] = strtok(NULL, ":")))
|
|
badline = 1;
|
|
}
|
|
if (badline) {
|
|
LOG(llevError, "Bad line in image_info file, ignoring line:\n %s", buf);
|
|
} else {
|
|
len = atoi(cps[0]);
|
|
if (len >= MAX_FACE_SETS) {
|
|
LOG(llevError, "To high a setnum in image_info file: %d > %d\n", len, MAX_FACE_SETS);
|
|
abort();
|
|
}
|
|
facesets[len].prefix = strdup_local(cps[1]);
|
|
facesets[len].fullname = strdup_local(cps[2]);
|
|
facesets[len].fallback = atoi(cps[3]);
|
|
facesets[len].size = strdup_local(cps[4]);
|
|
facesets[len].extension = strdup_local(cps[5]);
|
|
facesets[len].comment = strdup_local(cps[6]);
|
|
}
|
|
}
|
|
close_and_delete(infile, compressed);
|
|
for (i = 0; i < MAX_FACE_SETS; i++) {
|
|
if (facesets[i].prefix)
|
|
check_faceset_fallback(i, MAX_FACE_SETS);
|
|
}
|
|
/* Loaded the faceset information - now need to load up the
|
|
* actual faces.
|
|
*/
|
|
|
|
for (fileno = 0; fileno < MAX_FACE_SETS; fileno++) {
|
|
/* if prefix is not set, this is not used */
|
|
if (!facesets[fileno].prefix)
|
|
continue;
|
|
facesets[fileno].faces = calloc(nrofpixmaps, sizeof(face_info));
|
|
|
|
snprintf(filename, sizeof(filename), "%s/crossfire.%d", settings.datadir, fileno);
|
|
LOG(llevDebug, "Loading image file %s\n", filename);
|
|
|
|
if ((infile = open_and_uncompress(filename, 0, &compressed)) == NULL) {
|
|
LOG(llevError, "Unable to open %s\n", filename);
|
|
abort();
|
|
}
|
|
while (fgets(buf, HUGE_BUF-1, infile) != NULL) {
|
|
if (strncmp(buf, "IMAGE ", 6) != 0) {
|
|
LOG(llevError, "read_client_images:Bad image line - not IMAGE, instead\n%s", buf);
|
|
abort();
|
|
}
|
|
num = atoi(buf+6);
|
|
if (num < 0 || num >= nrofpixmaps) {
|
|
LOG(llevError, "read_client_images: Image num %d not in 0..%d\n%s", num, nrofpixmaps, buf);
|
|
abort();
|
|
}
|
|
/* Skip accross the number data */
|
|
for (cp = buf+6; *cp != ' '; cp++)
|
|
;
|
|
len = atoi(cp);
|
|
if (len == 0 || len > MAX_IMAGE_SIZE) {
|
|
LOG(llevError, "read_client_images: length not valid: %d > %d \n%s", len, MAX_IMAGE_SIZE, buf);
|
|
abort();
|
|
}
|
|
/* We don't actualy care about the name if the image that
|
|
* is embedded in the image file, so just ignore it.
|
|
*/
|
|
facesets[fileno].faces[num].datalen = len;
|
|
facesets[fileno].faces[num].data = malloc(len);
|
|
if ((i = fread(facesets[fileno].faces[num].data, len, 1, infile)) != 1) {
|
|
LOG(llevError, "read_client_images: Did not read desired amount of data, wanted %d, got %d\n%s", len, i, buf);
|
|
abort();
|
|
}
|
|
facesets[fileno].faces[num].checksum = 0;
|
|
for (i = 0; i < len; i++) {
|
|
ROTATE_RIGHT(facesets[fileno].faces[num].checksum);
|
|
facesets[fileno].faces[num].checksum += facesets[fileno].faces[num].data[i];
|
|
facesets[fileno].faces[num].checksum &= 0xffffffff;
|
|
}
|
|
}
|
|
close_and_delete(infile, compressed);
|
|
} /* For fileno < MAX_FACE_SETS */
|
|
}
|
|
|
|
/**
|
|
* Checks specified faceset is valid
|
|
* \param fsn faceset number
|
|
*/
|
|
int is_valid_faceset(int fsn) {
|
|
if (fsn >= 0 && fsn < MAX_FACE_SETS && facesets[fsn].prefix)
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Frees all faceset information
|
|
*/
|
|
void free_socket_images(void) {
|
|
int num, q;
|
|
|
|
for (num = 0; num < MAX_FACE_SETS; num++) {
|
|
if (facesets[num].prefix) {
|
|
for (q = 0; q < nrofpixmaps; q++)
|
|
if (facesets[num].faces[q].data)
|
|
free(facesets[num].faces[q].data);
|
|
free(facesets[num].prefix);
|
|
free(facesets[num].fullname);
|
|
free(facesets[num].size);
|
|
free(facesets[num].extension);
|
|
free(facesets[num].comment);
|
|
free(facesets[num].faces);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This returns the set we will actually use when sending
|
|
* a face. This is used because the image files may be sparse.
|
|
* This function is recursive. imageno is the face number we are
|
|
* trying to send
|
|
*
|
|
* If face is not found in specified faceset, tries with 'fallback' faceset.
|
|
*
|
|
* \param faceset faceset to check
|
|
* \param imageno image number
|
|
*
|
|
*/
|
|
int get_face_fallback(int faceset, int imageno) {
|
|
/* faceset 0 is supposed to have every image, so just return. Doing
|
|
* so also prevents infinite loops in the case if it not having
|
|
* the face, but in that case, we are likely to crash when we try
|
|
* to access the data, but that is probably preferable to an infinite
|
|
* loop.
|
|
*/
|
|
if (faceset == 0)
|
|
return 0;
|
|
|
|
if (!facesets[faceset].prefix) {
|
|
LOG(llevError, "get_face_fallback called with unused set (%d)?\n", faceset);
|
|
return 0; /* use default set */
|
|
}
|
|
if (facesets[faceset].faces[imageno].data)
|
|
return faceset;
|
|
return get_face_fallback(facesets[faceset].fallback, imageno);
|
|
}
|