/*
 * The Python Imaging Library
 * $Id$
 *
 * imaging palette object
 *
 * history:
 * 1996-05-05 fl   Added to library
 * 1996-05-27 fl   Added colour mapping stuff
 * 1997-05-12 fl   Support RGBA palettes
 * 2005-02-09 fl   Removed grayscale entries from web palette
 *
 * Copyright (c) Secret Labs AB 1997-2005.  All rights reserved.
 * Copyright (c) Fredrik Lundh 1995-1997.
 *
 * See the README file for information on usage and redistribution.
 */

#include "Imaging.h"

#include <math.h>

ImagingPalette
ImagingPaletteNew(const ModeID mode) {
    /* Create a palette object */

    int i;
    ImagingPalette palette;

    if (mode != IMAGING_MODE_RGB && mode != IMAGING_MODE_RGBA &&
        mode != IMAGING_MODE_CMYK) {
        return (ImagingPalette)ImagingError_ModeError();
    }

    palette = calloc(1, sizeof(struct ImagingPaletteInstance));
    if (!palette) {
        return (ImagingPalette)ImagingError_MemoryError();
    }

    palette->mode = mode;

    palette->size = 0;
    for (i = 0; i < 256; i++) {
        palette->palette[i * 4 + 3] = 255; /* opaque */
    }

    return palette;
}

ImagingPalette
ImagingPaletteNewBrowser(void) {
    /* Create a standard "browser" palette object */

    int i, r, g, b;
    ImagingPalette palette;

    palette = ImagingPaletteNew(IMAGING_MODE_RGB);
    if (!palette) {
        return NULL;
    }

    /* FIXME: Add 10-level windows palette here? */

    /* Simple 6x6x6 colour cube */
    i = 10;
    for (b = 0; b < 256; b += 51) {
        for (g = 0; g < 256; g += 51) {
            for (r = 0; r < 256; r += 51) {
                palette->palette[i * 4 + 0] = r;
                palette->palette[i * 4 + 1] = g;
                palette->palette[i * 4 + 2] = b;
                i++;
            }
        }
    }
    palette->size = i;

    /* FIXME: add 30-level grayscale wedge here? */

    return palette;
}

ImagingPalette
ImagingPaletteDuplicate(ImagingPalette palette) {
    /* Duplicate palette descriptor */

    ImagingPalette new_palette;

    if (!palette) {
        return NULL;
    }
    /* malloc check ok, small constant allocation */
    new_palette = malloc(sizeof(struct ImagingPaletteInstance));
    if (!new_palette) {
        return (ImagingPalette)ImagingError_MemoryError();
    }

    memcpy(new_palette, palette, sizeof(struct ImagingPaletteInstance));

    /* Don't share the cache */
    new_palette->cache = NULL;

    return new_palette;
}

void
ImagingPaletteDelete(ImagingPalette palette) {
    /* Destroy palette object */

    if (palette) {
        if (palette->cache) {
            free(palette->cache);
        }
        free(palette);
    }
}

/* -------------------------------------------------------------------- */
/* Colour mapping                                                       */
/* -------------------------------------------------------------------- */

/* This code is used to map RGB triplets to palette indices, using
   a palette index cache. */

/*
 * This implementation is loosely based on the corresponding code in
 * the IJG JPEG library by Thomas G. Lane.  Original algorithms by
 * Paul Heckbert and Spencer W. Thomas.
 *
 * The IJG JPEG library is copyright (C) 1991-1995, Thomas G. Lane.  */

#define DIST(a, b, s) (a - b) * (a - b) * s

/* Colour weights (no scaling, for now) */
#define RSCALE 1
#define GSCALE 1
#define BSCALE 1

/* Calculated scaled distances */
#define RDIST(a, b) DIST(a, b, RSCALE *RSCALE)
#define GDIST(a, b) DIST(a, b, GSCALE *GSCALE)
#define BDIST(a, b) DIST(a, b, BSCALE *BSCALE)

/* Incremental steps */
#define RSTEP (4 * RSCALE)
#define GSTEP (4 * GSCALE)
#define BSTEP (4 * BSCALE)

#define BOX 8

#define BOXVOLUME BOX * BOX * BOX

void
ImagingPaletteCacheUpdate(ImagingPalette palette, int r, int g, int b) {
    int i, j;
    unsigned int dmin[256], dmax;
    int r0, g0, b0;
    int r1, g1, b1;
    int rc, gc, bc;
    unsigned int d[BOXVOLUME];
    UINT8 c[BOXVOLUME];

    /* Get box boundaries for the given (r,g,b)-triplet.  Each box
       covers eight cache slots (32 colour values, that is). */

    r0 = r & 0xe0;
    r1 = r0 + 0x1f;
    rc = (r0 + r1) / 2;
    g0 = g & 0xe0;
    g1 = g0 + 0x1f;
    gc = (g0 + g1) / 2;
    b0 = b & 0xe0;
    b1 = b0 + 0x1f;
    bc = (b0 + b1) / 2;

    /* Step 1 -- Select relevant palette entries (after Heckbert) */

    /* For each palette entry, calculate the min and max distances to
     * any position in the box given by the colour we're looking for. */

    dmax = (unsigned int)~0;

    for (i = 0; i < palette->size; i++) {
        int r, g, b;
        unsigned int tmin, tmax;

        /* Find min and max distances to any point in the box */
        r = palette->palette[i * 4 + 0];
        tmin = (r < r0) ? RDIST(r, r0) : (r > r1) ? RDIST(r, r1) : 0;
        tmax = (r <= rc) ? RDIST(r, r1) : RDIST(r, r0);

        g = palette->palette[i * 4 + 1];
        tmin += (g < g0) ? GDIST(g, g0) : (g > g1) ? GDIST(g, g1) : 0;
        tmax += (g <= gc) ? GDIST(g, g1) : GDIST(g, g0);

        b = palette->palette[i * 4 + 2];
        tmin += (b < b0) ? BDIST(b, b0) : (b > b1) ? BDIST(b, b1) : 0;
        tmax += (b <= bc) ? BDIST(b, b1) : BDIST(b, b0);

        dmin[i] = tmin;
        if (tmax < dmax) {
            dmax = tmax; /* keep the smallest max distance only */
        }
    }

    /* Step 2 -- Incrementally update cache slot (after Thomas) */

    /* Find the box containing the nearest palette entry, and update
     * all slots in that box.  We only check boxes for which the min
     * distance is less than or equal the smallest max distance */

    for (i = 0; i < BOXVOLUME; i++) {
        d[i] = (unsigned int)~0;
    }

    for (i = 0; i < palette->size; i++) {
        if (dmin[i] <= dmax) {
            int rd, gd, bd;
            int ri, gi, bi;
            int rx, gx, bx;

            ri = (r0 - palette->palette[i * 4 + 0]) * RSCALE;
            gi = (g0 - palette->palette[i * 4 + 1]) * GSCALE;
            bi = (b0 - palette->palette[i * 4 + 2]) * BSCALE;

            rd = ri * ri + gi * gi + bi * bi;

            ri = ri * (2 * RSTEP) + RSTEP * RSTEP;
            gi = gi * (2 * GSTEP) + GSTEP * GSTEP;
            bi = bi * (2 * BSTEP) + BSTEP * BSTEP;

            rx = ri;
            for (r = j = 0; r < BOX; r++) {
                gd = rd;
                gx = gi;
                for (g = 0; g < BOX; g++) {
                    bd = gd;
                    bx = bi;
                    for (b = 0; b < BOX; b++) {
                        if ((unsigned int)bd < d[j]) {
                            d[j] = bd;
                            c[j] = (UINT8)i;
                        }
                        bd += bx;
                        bx += 2 * BSTEP * BSTEP;
                        j++;
                    }
                    gd += gx;
                    gx += 2 * GSTEP * GSTEP;
                }
                rd += rx;
                rx += 2 * RSTEP * RSTEP;
            }
        }
    }

    /* Step 3 -- Update cache */

    /* The c array now contains the closest match for each
     * cache slot in the box.  Update the cache. */

    j = 0;
    for (r = r0; r < r1; r += 4) {
        for (g = g0; g < g1; g += 4) {
            for (b = b0; b < b1; b += 4) {
                ImagingPaletteCache(palette, r, g, b) = c[j++];
            }
        }
    }
}

int
ImagingPaletteCachePrepare(ImagingPalette palette) {
    /* Add a colour cache to a palette */

    int i;
    int entries = 64 * 64 * 64;

    if (palette->cache == NULL) {
        /* The cache is 512k.  It might be a good idea to break it
           up into a pointer array (e.g. an 8-bit image?) */

        /* malloc check ok, small constant allocation */
        palette->cache = (INT16 *)malloc(entries * sizeof(INT16));
        if (!palette->cache) {
            (void)ImagingError_MemoryError();
            return -1;
        }

        /* Mark all entries as empty */
        for (i = 0; i < entries; i++) {
            palette->cache[i] = 0x100;
        }
    }

    return 0;
}

void
ImagingPaletteCacheDelete(ImagingPalette palette) {
    /* Release the colour cache, if any */

    if (palette && palette->cache) {
        free(palette->cache);
        palette->cache = NULL;
    }
}
