/* libppm3.c - ppm utility library part 3
**
** Colormap routines.
**
** Copyright (C) 1989, 1991 by Jef Poskanzer.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation.  This software is provided "as is" without express or
** implied warranty.
*/

#include "ppmcmap.h"

#define HASH_SIZE 20023

#ifdef PPM_PACKCOLORS
#define ppm_hashpixel(p) ( ( (int) (p) & 0x7fffffff ) % HASH_SIZE )
#else /*PPM_PACKCOLORS*/
#define ppm_hashpixel(p) ( ( ( (long) PPM_GETR(p) * 33023 + (long) PPM_GETG(p) * 30013 + (long) PPM_GETB(p) * 27011 ) & 0x7fffffff ) % HASH_SIZE )
#endif /*PPM_PACKCOLORS*/

colorhist_vector
ppm_computecolorhist(pixel** pixels,
                     int cols, int rows, int maxcolors,
                     int* colorsP)
    {
    colorhash_table cht;
    colorhist_vector chv;

    cht = ppm_computecolorhash( pixels, cols, rows, maxcolors, colorsP );
    if ( cht == (colorhash_table) 0 )
        return (colorhist_vector) 0;
    chv = ppm_colorhashtocolorhist( cht, maxcolors );
    ppm_freecolorhash( cht );
    return chv;
    }

void
ppm_addtocolorhist(colorhist_vector chv,
                   int* colorsP,
                   int maxcolors,
                   pixel* colorP,
                   int value, int position)
    {
    int i, j;

    /* Search colorhist for the color. */
    for ( i = 0; i < *colorsP; ++i )
        if ( PPM_EQUAL( chv[i].color, *colorP ) )
            {
            /* Found it - move to new slot. */
            if ( position > i )
                {
                for ( j = i; j < position; ++j )
                    chv[j] = chv[j + 1];
                }
            else if ( position < i )
                {
                for ( j = i; j > position; --j )
                    chv[j] = chv[j - 1];
                }
            chv[position].color = *colorP;
            chv[position].value = value;
            return;
            }
    if ( *colorsP < maxcolors )
        {
        /* Didn't find it, but there's room to add it; so do so. */
        for ( i = *colorsP; i > position; --i )
            chv[i] = chv[i - 1];
        chv[position].color = *colorP;
        chv[position].value = value;
        ++(*colorsP);
        }
    }

colorhash_table
ppm_computecolorhash(pixel** pixels,
                     int cols, int rows, int maxcolors,
                     int* colorsP)
    {
    colorhash_table cht;
    register pixel* pP;
    colorhist_list chl;
    int col, row, hash;

    cht = ppm_alloccolorhash( );
    *colorsP = 0;

    /* Go through the entire image, building a hash table of colors. */
    for ( row = 0; row < rows; ++row )
        for ( col = 0, pP = pixels[row]; col < cols; ++col, ++pP )
            {
            hash = ppm_hashpixel( *pP );
            for ( chl = cht[hash]; chl != (colorhist_list) 0; chl = chl->next )
                if ( PPM_EQUAL( chl->ch.color, *pP ) )
                    break;
            if ( chl != (colorhist_list) 0 )
                ++(chl->ch.value);
            else
                {
                if ( ++(*colorsP) > maxcolors )
                    {
                    ppm_freecolorhash( cht );
                    return (colorhash_table) 0;
                    }
                chl = (colorhist_list) malloc( sizeof(struct colorhist_list_item) );
                if ( chl == 0 )
                    pm_error( "out of memory computing hash table" );
                chl->ch.color = *pP;
                chl->ch.value = 1;
                chl->next = cht[hash];
                cht[hash] = chl;
                }
            }
    
    return cht;
    }

colorhash_table
ppm_alloccolorhash( )
    {
    colorhash_table cht;
    int i;

    cht = (colorhash_table) malloc( HASH_SIZE * sizeof(colorhist_list) );
    if ( cht == 0 )
        pm_error( "out of memory allocating hash table" );

    for ( i = 0; i < HASH_SIZE; ++i )
        cht[i] = (colorhist_list) 0;

    return cht;
    }

int
ppm_addtocolorhash(colorhash_table cht,
                   pixel* colorP,
                   int value)
    {
    register int hash;
    register colorhist_list chl;

    chl = (colorhist_list) malloc( sizeof(struct colorhist_list_item) );
    if ( chl == 0 )
        return -1;
    hash = ppm_hashpixel( *colorP );
    chl->ch.color = *colorP;
    chl->ch.value = value;
    chl->next = cht[hash];
    cht[hash] = chl;
    return 0;
    }

colorhist_vector
ppm_colorhashtocolorhist(colorhash_table cht,
                         int maxcolors)
    {
    colorhist_vector chv;
    colorhist_list chl;
    int i, j;

    /* Now collate the hash table into a simple colorhist array. */
    chv = (colorhist_vector) malloc( maxcolors * sizeof(struct colorhist_item) );
    /* (Leave room for expansion by caller.) */
    if ( chv == (colorhist_vector) 0 )
        pm_error( "out of memory generating histogram" );

    /* Loop through the hash table. */
    j = 0;
    for ( i = 0; i < HASH_SIZE; ++i )
        for ( chl = cht[i]; chl != (colorhist_list) 0; chl = chl->next )
            {
            /* Add the new entry. */
            chv[j] = chl->ch;
            ++j;
            }

    /* All done. */
    return chv;
    }

colorhash_table
ppm_colorhisttocolorhash(colorhist_vector chv,
                         int colors)
    {
    colorhash_table cht;
    int i, hash;
    pixel color;
    colorhist_list chl;

    cht = ppm_alloccolorhash( );

    for ( i = 0; i < colors; ++i )
        {
        color = chv[i].color;
        hash = ppm_hashpixel( color );
        for ( chl = cht[hash]; chl != (colorhist_list) 0; chl = chl->next )
            if ( PPM_EQUAL( chl->ch.color, color ) )
                pm_error(
                    "same color found twice - %d %d %d", PPM_GETR(color),
                    PPM_GETG(color), PPM_GETB(color) );
        chl = (colorhist_list) malloc( sizeof(struct colorhist_list_item) );
        if ( chl == (colorhist_list) 0 )
            pm_error( "out of memory" );
        chl->ch.color = color;
        chl->ch.value = i;
        chl->next = cht[hash];
        cht[hash] = chl;
        }

    return cht;
    }

int
ppm_lookupcolor(colorhash_table cht,
                pixel* colorP)
    {
    int hash;
    colorhist_list chl;

    hash = ppm_hashpixel( *colorP );
    for ( chl = cht[hash]; chl != (colorhist_list) 0; chl = chl->next )
        if ( PPM_EQUAL( chl->ch.color, *colorP ) )
            return chl->ch.value;

    return -1;
    }

void
ppm_freecolorhist(colorhist_vector chv)
    {
    free( (char*) chv );
    }

void
ppm_freecolorhash(colorhash_table cht)
    {
    int i;
    colorhist_list chl, chlnext;

    for ( i = 0; i < HASH_SIZE; ++i )
        for ( chl = cht[i]; chl != (colorhist_list) 0; chl = chlnext )
            {
            chlnext = chl->next;
            free( (char*) chl );
            }
    free( (char*) cht );
    }
