view libmaputils.c @ 1770:cc59f80b0e78

Various cleanups.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 27 Oct 2017 04:35:51 +0300
parents 5dda4803c59b
children f4c49fd6557e
line wrap: on
line source

/*
 * maputils - Generic functions/tables for maputils package
 * Programmed by Matti 'ccr' Hämäläinen (Ggr Pupunen)
 * (C) Copyright 2006-2017 Tecnic Software productions (TNSP)
 */
#include "libmaputils.h"


const MapColor mapColors[] =
{
    { 0x00, 0x00, 0x00, 30,    ANSI_OFF,  FALSE }, // col_black
    { 0x00, 0x00, 0xaa, 34,    ANSI_OFF,  FALSE }, // col_blue
    { 0xaa, 0x00, 0x00, 31,    ANSI_OFF,  FALSE }, // col_red
    { 0xaa, 0xaa, 0x00, 33,    ANSI_OFF,  FALSE }, // col_yellow
    { 0x00, 0xaa, 0x00, 32,    ANSI_OFF,  FALSE }, // col_green
    { 0xaa, 0xaa, 0xaa, 37,    ANSI_OFF,  FALSE }, // col_white
    { 0x00, 0xff, 0xff, 36,    ANSI_OFF,  FALSE }, // col_cyan
    { 0x77, 0x00, 0x77, 35,    ANSI_OFF,  FALSE }, // col_magenta

    { 0x77, 0x77, 0x77, 30,    ANSI_BOLD, FALSE }, // col_light_black
    { 0x00, 0x00, 0xff, 34,    ANSI_BOLD, FALSE }, // col_light_blue
    { 0xee, 0x00, 0x00, 31,    ANSI_BOLD, FALSE }, // col_light_red
    { 0xff, 0xff, 0x00, 33,    ANSI_BOLD, FALSE }, // col_light_yellow
    { 0x00, 0xff, 0x00, 32,    ANSI_BOLD, FALSE }, // col_light_green
    { 0xff, 0xff, 0xff, 37,    ANSI_BOLD, FALSE }, // col_light_white
    { 0x00, 0xff, 0xff, 36,    ANSI_BOLD, FALSE }, // col_light_cyan
    { 0xff, 0x00, 0xff, 35,    ANSI_BOLD, FALSE }, // col_light_magenta

    // extra colors, not in ANSI
    { 0xff, 0x00, 0x00, 31,    ANSI_BOLD, TRUE  }, // col_very_light_red
};

const int nmapColors = sizeof(mapColors) / sizeof(mapColors[0]);


const MapPiece mapPieces[] =
{
{ '!', "Mountain Peak",     0xcc,0xff,0xff, col_white,          -1, },
{ '#', "Ruins",             0x88,0x88,0x88, col_light_black,    -1, },
{ '%', "Special Location",  0xff,0xff,0xff, col_light_white,    -1, },
{ '+', "Crossing",          0x33,0x33,0x33, col_light_black,    -1, },
{ '-', "Road",              0x33,0x33,0x33, col_light_black,    -1, },
{ '|', "Road",              0x33,0x33,0x33, col_light_black,    -1, },
{ '/', "Road",              0x33,0x33,0x33, col_light_black,    -1, },
{ '\\',"Road",              0x33,0x33,0x33, col_light_black,    -1, },
{ '.', "Plains",            0x55,0x92,0x00, col_green,          -1, },
{ '=', "Bridge",            0x33,0x33,0x33, col_light_black,    -1, },
{ '?', "Scenic Location",   0xff,0xff,0xff, col_light_white,    -1, },
{ '@', "Flowing Lava",      0xff,0x99,0x3f, col_very_light_red, -1, },
{ 'C', "Player City",       0x88,0x88,0x88, col_light_black,    -1, },
{ 'F', "Deep Forest",       0x00,0x88,0x00, col_green,          -1, },
{ 'H', "Highlands",         0x66,0x3f,0x00, col_magenta,        -1, },
{ 'L', "Lava Lake",         0xff,0x50,0x00, col_very_light_red, -1, },
{ 'R', "Deep River",        0x33,0x66,0xff, col_blue,           -1, },
{ 'V', "Volcano",           0xff,0x33,0x00, col_red,            -1, },
{ '^', "Mountain",          0x71,0x82,0x92, col_light_magenta,  -1, },
{ 'b', "Beach",             0xcf,0xc4,0xa5, col_yellow,         -1, },
{ 'c', "City",              0x88,0x88,0x88, col_light_black,    -1, },
{ 'd', "Desert",            0xee,0xaa,0x22, col_yellow,         -1, },
{ 'f', "Forest",            0x00,0xb6,0x00, col_light_green,    -1, },
{ 'h', "Hills",             0x99,0x66,0x00, col_magenta,        -1, },
{ 'i', "Ice",               0xee,0xee,0xff, col_light_blue,     -1, },
{ 'j', "Jungle",            0x13,0x96,0x36, col_green,          -1, },
{ 'l', "Lake",              0x21,0x33,0xcc, col_light_blue,     -1, },
{ 'r', "River",             0x66,0x99,0xff, col_light_blue,     -1, },
{ 's', "Swamp",             0x9d,0xa8,0x0a, col_light_red,      -1, },
{ 't', "Tundra",            0x61,0xc3,0xa2, col_white,          -1, },
{ 'v', "Valley",            0x22,0xdd,0x22, col_light_green,    -1, },
{ 'w', "Waterfall",         0x77,0xaa,0xff, col_light_cyan,     -1, },
{ 'x', "Badlands",          0x8a,0x83,0x60, col_light_red,      -1, },
{ 'y', "Fields",            0xa7,0xcc,0x14, col_yellow,         -1, },
{ 'z', "Shore",             0xa7,0xcc,0x14, col_light_yellow,   -1, },
{ ',', "Muddy Trail",       0x8c,0x57,0x38, col_light_yellow,   -1, },
{ '&', "Monster",           0xff,0x00,0x00, col_light_red,      -1, },
#ifndef SECRET_MAP_DATA_FORMAT
{ 'S', "Shallows",          0x44,0xcc,0xcc, col_light_cyan,     -1, },
{ '~', "Sea",               0x11,0x88,0xdd, col_blue,           -1, },
#else
{ '~', "Sea 1",             0x00,0x11,0x88, col_blue,           -1, },
{ '"', "Sea 2",             0x11,0x22,0x99, col_blue,           -1, },
{ '\'',"Sea 3",             0x11,0x33,0xaa, col_blue,           -1, },
{ '`', "Shallows?",         0x11,0x66,0xdd, col_blue,           -1, },
{ 'p', "Plains?",           0x55,0x92,0x00, col_green,          -1, },
{ 'S', "Swamp?",            0x77,0x77,0x33, col_yellow,         -1, },
#endif
{ -1 , "Road",              0x33,0x33,0x33, col_light_black,    '.', },
{ -1 , "Plains",            0x00,0xff,0x00, col_light_green,    'p', },
{ -1 , "Highlands",         0x77,0x00,0x77, col_magenta,        'i', },
};

const int nmapPieces = sizeof(mapPieces) / sizeof(mapPieces[0]);


const MapPiece mapCityPieces[] =
{
{ '.', "Street",            0xaa,0xaa,0x00, col_yellow,         -1, },
{ '-', "Door/Gate",         0xff,0xff,0x00, col_light_yellow,   -1, },
{ '|', "Door/Gate",         0xff,0xff,0x00, col_light_yellow,   -1, },
{ '=', "Bridge/Gate",       0x33,0x33,0x33, col_yellow,         -1, },
{ '*', "Fountain",          0x00,0x00,0xff, col_light_blue,     -1, },
{ '@', "???",               0xff,0xff,0x00, col_light_yellow,   -1, },
{ '$', "Tree",              0x00,0xaa,0x00, col_green,          -1, },
{ '#', "Wall",              0x33,0x33,0x33, col_light_black,    -1, },
{ '"', "Grass",             0x00,0x88,0x00, col_green,          -1, },
{ ':', "???",               0x00,0x00,0x00, col_light_white,    -1, },
{ 'z', "Shore",             0xa7,0xcc,0x14, col_yellow,         -1, },
{ ',', "Lawn",              0x00,0xcc,0x00, col_green,          -1, },

// Old versions
{  -1, "Street",            0x00,0x00,0x00, col_white,          '.', },
{  -1, "Door/Gate",         0x00,0x00,0x00, col_yellow,         '-', },
{  -1, "Door/Gate",         0x00,0x00,0x00, col_yellow,         '|', },
{  -1, "Fountain",          0x00,0x00,0x00, col_light_white,    '*', },

};

const int nmapCityPieces = sizeof(mapCityPieces) / sizeof(mapCityPieces[0]);


typedef struct
{
    unsigned char c;
    char *ent;
} HTMLEntity;


static const HTMLEntity HTMLEntities[] =
{
    { '&', "amp" },
    { '<', "lt" },
    { '>', "gt" },
    { '"', "quot" },

    { 228, "#228" },
    { 246, "#246" },
    { 196, "#196" },
    { 214, "#214" },
};

static const int numHTMLEntities = sizeof(HTMLEntities) / sizeof(HTMLEntities[0]);


int tmpl_fprintve(int (*mputs)(const char *, FILE *), FILE *outFile, const char *fmt, va_list ap)
{
    int n, bufsize = strlen(fmt) * 2;
    char *buf, *tmp;

    if ((buf = th_malloc(bufsize)) == NULL)
        return -1;

    while (1)
    {
        va_list tap;
        va_copy(tap, ap);
        n = vsnprintf(buf, bufsize, fmt, tap);
        va_end(tap);
        if (n > -1 && n < bufsize)
        {
            // String fit the buffer, print it out and return
            int ret = mputs(buf, outFile);
            if (ret < 0)
                return ret;

            th_free(buf);
            return n;
        }

        // Didn't fit, try reallocating some more space
        if (n > -1)
            bufsize = n + 1;
        else
            bufsize *= 2;

        if ((tmp = th_realloc(buf, bufsize)) == NULL)
        {
            th_free(buf);
            return -2;
        }
        else
            buf = tmp;
    }

    return 0;
}


int fputse(const char *str, FILE *outFile)
{
    const char *s = str;
    if (str == NULL)
        return EOF;

    while (*s)
    {
        int i;
        BOOL found = FALSE;

        for (i = 0; i < numHTMLEntities; i++)
        if (HTMLEntities[i].c == *s)
        {
            fprintf(outFile, "&%s;", HTMLEntities[i].ent);
            found = TRUE;
            break;
        }

        if (!found)
            fputc(*s, outFile);
        s++;
    }

    return 0;
}


int fprintve(FILE *outFile, const char *fmt, va_list ap)
{
    return tmpl_fprintve(fputse, outFile, fmt, ap);
}


int fprintfe(FILE *outFile, const char *fmt, ...)
{
    int ret;
    va_list ap;
    va_start(ap, fmt);
    ret = fprintve(outFile, fmt, ap);
    va_end(ap);
    return ret;
}


int fputsesc1(const char *str, FILE *f)
{
    const char *p = str;
    if (str == NULL)
        return -1;

    while (*p)
    {
        int ret;
        switch (*p)
        {
            case '\\': ret = fputs("\\", f); break;
            case '"': ret = fputs("\\\"", f); break;
            default: ret = fputc(*p, f); break;
        }
        p++;
        if (ret < 0)
            return ret;
    }

    return 0;
}


int fprintvesc1(FILE *outFile, const char *fmt, va_list ap)
{
    return tmpl_fprintve(fputsesc1, outFile, fmt, ap);
}


int fprintfesc1(FILE *outFile, const char *fmt, ...)
{
    int ret;
    va_list ap;
    va_start(ap, fmt);
    ret = fprintvesc1(outFile, fmt, ap);
    va_end(ap);
    return ret;
}


int fputsesc3(const char *str, FILE *f)
{
    const char *p = str;
    if (str == NULL)
        return -1;

    while (*p)
    {
        int ret;
        switch (*p)
        {
            case ';': ret = fputs("\\;", f); break;
            case '\\': ret = fputs("\\", f); break;
            case '\'': ret = fputs("\\'", f); break;
            case '"': ret = fputs("\\\"", f); break;
            default: ret = fputc(*p, f); break;
        }
        p++;
        if (ret < 0)
            return ret;
    }

    return 0;
}


int fputsesc2(const char *str, FILE *f)
{
    const char *p = str;
    if (str == NULL)
        return -1;

    while (*p)
    {
        int ret;
        switch (*p)
        {
            case ';': ret = fputs("\\;", f); break;
            case '\\': ret = fputs("\\", f); break;
            default: ret = fputc(*p, f); break;
        }
        p++;
        if (ret < 0)
            return ret;
    }

    return 0;
}


char *muColorToCSSColor(char *buf, const size_t len, const int c)
{
    assert(c >= 0);
    assert(c < nmapColors);

    snprintf(buf, len, "#%02x%02x%02x",
        mapColors[c].r, mapColors[c].g, mapColors[c].b);

    return buf;
}


static int muGetPieceFromList(const MapPiece pieces[], const int npieces, int symbol, BOOL getOld)
{
    int i;

    for (i = 0; i < npieces; i++)
    {
        if ((getOld && pieces[i].oldSymbol == symbol) ||
            (!getOld && pieces[i].symbol == symbol))
            return i;
    }

    for (i = 0; i < npieces; i++)
    {
        if (getOld && pieces[i].symbol == symbol)
            return i;
    }

    return -1;
}


int muGetMapPieceIndex(int symbol, BOOL getOld, BOOL getCity)
{
    int n;

    if (getCity && (n = muGetPieceFromList(mapCityPieces, nmapCityPieces, symbol, getOld)) >= 0)
        return n;

    return muGetPieceFromList(mapPieces, nmapPieces, symbol, getOld);
}


static int muGetColorFromList(const MapPiece pieces[], const int npieces, int symbol, BOOL getOld)
{
    int i;

    if (getOld)
    {
        for (i = 0; i < npieces; i++)
            if (pieces[i].oldSymbol == symbol)
                return pieces[i].color;
    }

    for (i = 0; i < npieces; i++)
        if (pieces[i].symbol == symbol)
             return pieces[i].color;

    return -1;
}


int muGetMapPieceColor(int symbol, BOOL getOld, BOOL getCity)
{
    int n;

    if (getCity && (n = muGetColorFromList(mapCityPieces, nmapCityPieces, symbol, getOld)) >= 0)
        return n;

    return ((n = muGetColorFromList(mapPieces, nmapPieces, symbol, getOld)) >= 0) ? n : 0;
}


void muPrintHTMLhead(FILE *outFile, const char *title, BOOL html5)
{
    static const char *strCharSet = "utf-8";
    assert(outFile != NULL);

    if (html5)
    {
        fprintf(outFile,
        "<!DOCTYPE html>\n"
        "<html lang=\"en\">\n"
        "<head>\n"
        " <meta charset=\"%s\">\n",
        strCharSet);
    }
    else
    {
        fprintf(outFile,
        "<?xml version=\"1.0\" encoding=\"%s\"?>\n"
        "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" "
        "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
        "<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\" xml:lang=\"en\">\n"
        "<head>\n"
        " <meta http-equiv=\"Content-Type\" content=\"text/html; charset=%s\" />\n",
        strCharSet,
        strCharSet);
    }

    if (title)
    {
        fprintf(outFile, " <title>");
        fputse(title, outFile);
        fprintf(outFile, "</title>\n");
    }
}


void muPrintHTMLcolors(FILE *outFile, const char *tagName, const char *propName, const char *extra)
{
    int n;
    assert(outFile != NULL);

    for (n = 0; n < nmapColors; n++)
    {
        fprintf(outFile,
        "  %s.%c { %s: #%02x%02x%02x;%s%s }\n",
        tagName, 'a'+n, propName ? propName : "color",
        mapColors[n].r, mapColors[n].g, mapColors[n].b,
        mapColors[n].bold ? " font-weight: bold;" : "",
        extra ? extra : "");
    }
}


int muCopyFileToStream(FILE *outFile, const char *filename)
{
    FILE *inFile;

    if ((inFile = fopen(filename, "rb")) != NULL)
    {
        int c;
        while ((c = fgetc(inFile)) != EOF)
        {
            if (fputc(c, outFile) == EOF)
            {
                fclose(inFile);
                return -2;
            }
        }
        fclose(inFile);
        return 0;
    }
    else
        return -1;
}


/* Map block handling
 */
MapBlock * mapBlockAlloc(const int width, const int height)
{
    MapBlock *res;

    // Check arguments
    if (width <= 0 || height <= 0)
        return NULL;

    // Allocate struct and data
    res = (MapBlock *) th_malloc0(sizeof(MapBlock));
    if (!res) return NULL;

    res->width = width;
    res->height = height;
    res->scansize = ((width / BLOCK_SCAN_ALIGN) + 1) * BLOCK_SCAN_ALIGN;
    res->size = res->height * res->scansize * sizeof(char);

    res->data = (unsigned char *) th_malloc0(res->size);
    if (!res->data)
    {
        th_free(res);
        return NULL;
    }

    return res;
}


void mapBlockFree(MapBlock *block)
{
    if (block)
    {
        th_free(block->data);
        block->data = NULL;
        th_free(block);
    }
}


MapBlock * mapBlockCopy(MapBlock *block)
{
    MapBlock *res;

    if (block == NULL)
        return NULL;

    res = (MapBlock *) th_malloc0(sizeof(MapBlock));
    if (!res) return NULL;

    res->width    = block->width;
    res->height   = block->height;
    res->scansize = block->scansize;
    res->size     = block->size;

    res->data = (unsigned char *) th_malloc(res->size);
    if (!res->data)
    {
        th_free(res);
        return NULL;
    }

    memcpy(res->data, block->data, res->size);

    return res;
}


/* Parse single arbitrary sized block from given mapfile
 */
MapBlock * mapBlockParseFile(const char *filename, BOOL isDiff)
{
    MapBlock *block;
    FILE *inFile;

    if ((inFile = fopen(filename, "rb")) == NULL)
    {
        THERR("Could not open mapfile '%s' for reading.\n", filename);
        return NULL;
    }

    block = mapBlockParseStream(filename, inFile, isDiff);

    fclose(inFile);

    return block;
}


MapBlock * mapBlockParseStream(const char *filename, FILE *inFile, BOOL isDiff)
{
    MapBlock *res;
    unsigned char *o;
    long pos = 0L;
    BOOL flag;
    int x, y, resW, resH, c;
    assert(filename != NULL);

    if (isDiff)
    {
        char buf[128];
        int ver;
        if (fgets(buf, sizeof(buf), inFile) == NULL)
        {
            THERR("Failed to read file header from '%s': %s\n",
                filename, th_error_str(th_get_error()));
            return NULL;
        }
        if (sscanf(buf, DIFF_MAGIC "%d\n", &ver) != 1)
        {
            THERR("Not a DIFF format file '%s'.\n", filename);
            return NULL;
        }
        if (ver != DIFF_VERSION)
        {
            THERR("Not a correct DIFF format version (%d != %d) '%s'.\n",
                ver, DIFF_VERSION, filename);
            return NULL;
        }
        pos = ftell(inFile);
    }

    // Probe map width
    resH = 1;
    resW = -1;
    x = 0;
    flag = FALSE;
    while ((c = fgetc(inFile)) != EOF)
    {
        if ((!isDiff && c == '\n') || (isDiff && c == 0xff))
        {
            if (x > resW)
                resW = x;
            flag = TRUE;
        }
        else
        {
            if (flag)
            {
                x = 0;
                resH++;
                flag = FALSE;
            }
            x++;
        }
    }

    // Seek back
    if (fseek(inFile, pos, SEEK_SET) == -1)
    {
        THERR("Could not rewind file '%s'.\n",
            filename);
        return NULL;
    }

    // Allocate block
    if ((res = mapBlockAlloc(resW, resH)) == NULL)
    {
        THERR("Could not allocate mapblock (%d, %d) for '%s'.\n",
            resW, resH, filename);
        return NULL;
    }

    // Read data
    o = res->data;
    y = 0;
    x = 0;
    flag = FALSE;
    while ((c = fgetc(inFile)) != EOF && (y < res->height))
    {
        if ((!isDiff && c == '\n') || (isDiff && c == 0xff))
        {
            if (x != res->width)
            {
                THERR("Broken block in '%s', line #%d width %d < %d!\n",
                    filename, y, x, res->width);
                mapBlockFree(res);
                return NULL;
            }
            flag = TRUE;
        }
        else
        {
            if (flag)
            {
                x = 0;
                y++;
                o = res->data + (y * res->scansize);
                flag = FALSE;
            }
            o[x++] = c;

            if (x > res->scansize)
            {
                THERR("Broken block in '%s', line #%d width %d > scansize %d!\n",
                    filename, y, x, res->scansize);
                mapBlockFree(res);
                return NULL;
            }
        }
    }

    // Close file
    if (y >= res->height)
    {
        THERR("Broken block in '%s', height %d >= %d\n", filename, y, res->height);
        mapBlockFree(res);
        return NULL;
    }

    return res;
}


/* Print block to given output stream
 */
void mapBlockPrint(FILE *f, MapBlock *map)
{
    unsigned char *c;
    int x, y;
    assert(f != NULL);
    assert(map != NULL);


    for (y = 0; y < map->height; y++)
    {
        c = map->data + (y * map->scansize);
        for (x = 0; x < map->width; x++)
        {
            if (*c > 0)
                fputc(*c, f);
            else
                fputc(' ', f);

            c++;
        }
        fprintf(f, "\n");
    }
}


/* Blit a block into another, assume that memory has been allocated
 * in sufficient way and other preparations are done.
 */
int mapBlockPutDo(MapBlock *map, const MapBlock *src, const int ox, const int oy)
{
    assert(map != NULL);
    assert(b != NULL);

    for (int y = 0; y < src->height; y++)
    for (int x = 0; x < src->width; x++)
    {
        const int dx = ox + x;
        const int dy = oy + y;

        if (dx >= 0 && dx < map->width && dy >= 0 && dy < map->height)
        {
            char c = src->data[(y * src->scansize) + x];

            if (c != 0)
                map->data[(dy * map->scansize) + dx] = c;
        }
        else
            return -1;
    }

    return 0;
}


int mapBlockPut(MapBlock **pmap, const MapBlock *src, int ox, int oy)
{
    MapBlock *tmp;
    int x0, y0, x1, y1, mx, my;

    assert(pmap != NULL);
    assert(*pmap != NULL);
    assert(src != NULL);

    // Determine new block size
    x0 = mx = y0 = my = 0;
    x1 = (*pmap)->width - 1;
    y1 = (*pmap)->height - 1;

    if (ox < 0) { x0 = ox; mx = -ox; ox = 0; }
    if (oy < 0) { y0 = oy; my = -oy; oy = 0; }

    if ((x0 + ox + src->width - 1) > x1)
        x1 = (x0 + ox + src->width - 1);

    if ((y0 + oy + src->height - 1) > y1)
        y1 = (y0 + oy + src->height - 1);

    // Allocate new block
    if ((tmp = mapBlockAlloc(x1 - x0 + 1, y1 - y0 + 1)) == NULL)
        return -1;

    // Copy data
    if (mapBlockPutDo(tmp, *pmap, mx, my) < 0)
    {
        th_free(tmp);
        return -2;
    }

    if (mapBlockPutDo(tmp, src, ox, oy) < 0)
    {
        th_free(tmp);
        return -3;
    }

    tmp->x = -mx;
    tmp->y = -my;

    // Out with the old, in with the new
    mapBlockFree(*pmap);
    *pmap = tmp;

    return 0;
}


/* Clean given block from position markers and whitespaces
 */
void mapBlockClean(MapBlock *map, char *symbols)
{
    assert(map != NULL);
    assert(symbols != NULL);

    for (int y = 0; y < map->height; y++)
    {
        unsigned char *dp = map->data + (y * map->scansize);
        for (int x = 0; x < map->width; x++)
        {
            if (strchr(symbols, *dp) != NULL)
                *dp = 0;
            dp++;
        }
    }
}


void mapBlockPrintRaw(FILE *f, MapBlock *map)
{
    assert(f != NULL);
    assert(map != NULL);

    for (int y = 0; y < map->height; y++)
    {
        unsigned char *dp = map->data + (y * map->scansize);
        for (int x = 0; x < map->width; x++)
        {
            fputc(*dp, f);
            dp++;
        }

        fputc(0xff, f);
    }
}


int mapBlockGetEntropy(MapBlock *map, const unsigned char *exclude, const int nexclude)
{
    unsigned char *list;
    int x, y, i, num;

    // Allocate memory for entropy array
    if ((list = th_malloc0(256)) == NULL)
        return -1;

    // Collect sums into entropy array
    for (y = 0; y < map->height; y++)
    {
        unsigned char *c = map->data + (y * map->scansize);
        for (x = 0; x < map->width; x++)
        {
            list[*c]++;
            c++;
        }
    }

    // Handle exclusions
    if (exclude != NULL && nexclude > 0)
    {
        for (i = 0; i < nexclude; i++)
            list[(int) exclude[i]] = 0;
    }

    // Calculate simple entropy
    for (num = 0, i = 0; i < 256; i++)
        if (list[i]) num++;

    th_free(list);
    return num;
}