#   include	"bitmapConfig.h"

#   include	"bmRender.h"
#   include	<string.h>
#   include	<stdlib.h>
#   include	<appDebugon.h>

/************************************************************************/
/*									*/
/*  Color reduction.							*/
/*									*/
/************************************************************************/

#   define	LOG_CUTS	0
#   define	LOG_PALETTE	0

typedef struct HistogramEntry
    {
    int			heCount;
    RGB8Color		heColor;
    } HistogramEntry;

#   define	heRed	heColor.rgb8Red
#   define	heGreen	heColor.rgb8Green
#   define	heBlue	heColor.rgb8Blue

typedef struct	HashBucket
    {
    HistogramEntry	hbHistogramEntry;
    int			hbNumber;
    struct HashBucket *	hbNext;
    } HashBucket;

#   define	hbCount	hbHistogramEntry.heCount
#   define	hbRed	hbHistogramEntry.heColor.rgb8Red
#   define	hbGreen	hbHistogramEntry.heColor.rgb8Green
#   define	hbBlue	hbHistogramEntry.heColor.rgb8Blue

typedef struct ColorBox
    {
    int		cbHistogramOffset;
    int		cbColorCount;
    int		cbPixelCount;
    int		cbCutNumber;

    int		cbRMin;
    int		cbRMax;
    int		cbGMin;
    int		cbGMax;
    int		cbBMin;
    int		cbBMax;
    } ColorBox;

typedef struct CutNode
    {
    unsigned int	cnLeft;
    unsigned int	cnRight;
    unsigned char	cnLeftMaximum;
    unsigned char	cnComponent;
    } CutNode;

#   define	CN_LEAF		0
#   define	CN_RED		1
#   define	CN_GREEN	2
#   define	CN_BLUE		3

typedef struct ColorReducer
    {
    HashBucket **		crHashTable;
    HistogramEntry *		crHistogram;
    const ColorPalette *	crPalette;	/*  Borrowed from target */
    CutNode *			crCutNodes;
    int				crColorsCounted;
    } ColorReducer;

/************************************************************************/
/*									*/
/*  Color hash varia.							*/
/*									*/
/************************************************************************/

#define HASH_SIZE 6553

#define ppm_hash(r,g,b)	((( (int) (r) * 33023 +    \
			    (int) (g) * 30013 +    \
			    (int) (b) * 27011 ) & 0x7fffffff) % HASH_SIZE )

static void bmFreeColorHash(	HashBucket **	hashTable	)
    {
    int			i;

    for ( i= 0; i < HASH_SIZE; i++ )
	{
	HashBucket *	hashBucket= hashTable[i];

	while( hashBucket )
	    {
	    HashBucket *	nx= hashBucket->hbNext;
	    free( (char *)hashBucket );
	    hashBucket= nx;
	    }
	}

    return;
    }

/************************************************************************/
/*									*/
/*  Insert a new hash item: Convenience routine.			*/
/*									*/
/************************************************************************/

static int bmInsertHash(	HashBucket **	hashTable,
				int		hash,
				int		number,
				int		r,
				int		g,
				int		b )
    {
    HashBucket *	hashBucket= (HashBucket *)malloc( sizeof(HashBucket) );

    if  ( ! hashBucket )
	{ XDEB(hashBucket); return -1;	}

    hashBucket->hbNext= hashTable[hash];
    hashTable[hash]= hashBucket;

    hashBucket->hbCount= 1;
    hashBucket->hbNumber= number;

    hashBucket->hbRed= r;
    hashBucket->hbGreen= g;
    hashBucket->hbBlue= b;

    return 0;
    }

/************************************************************************/
/*									*/
/*  Utility functions: Manage a color reducer.				*/
/*									*/
/************************************************************************/

static void bmInitColorReducer(	ColorReducer *	cr )
    {
    cr->crHashTable= (HashBucket **)0;
    cr->crHistogram= (HistogramEntry *)0;
    cr->crPalette= (ColorPalette *)0;
    cr->crCutNodes= (CutNode *)0;

    return;
    }

static void bmCleanColorReducer( ColorReducer *	cr )
    {
    if  ( cr->crHistogram )
	{ free( cr->crHistogram );		}
    if  ( cr->crCutNodes )
	{ free( cr->crCutNodes );		}
    if  ( cr->crHashTable )
	{ bmFreeColorHash( cr->crHashTable );	}

    return;
    }

static int bmAllocateCutNodes(		ColorReducer *	cr,
					int		colorCount )
    {
    CutNode *	fresh;

    fresh= (CutNode *)realloc( cr->crCutNodes, 2* colorCount* sizeof(CutNode) );
    if  ( ! fresh )
	{ LLDEB(colorCount,fresh); return -1;	} 

    cr->crCutNodes= fresh;
    memset( cr->crCutNodes, 0, 2* colorCount* sizeof(CutNode) );

    return 0;
    }

static int bmAllocateHistogram(		ColorReducer *	cr,
					int		colorCount )
    {
    HistogramEntry *	fresh;

    fresh= (HistogramEntry *)realloc( cr->crHistogram,
					colorCount* sizeof(HistogramEntry) );
    if  ( ! fresh )
	{ LLDEB(colorCount,fresh); return -1; }

    cr->crHistogram= fresh;

    return 0;
    }

/************************************************************************/
/*									*/
/*  Determine median.							*/
/*									*/
/************************************************************************/

static int bmHistRedMedian(	int *			pPixelCount,
				int *			pColorCount,
				int			pixelCount,
				HistogramEntry *	histogram,
				int			colorCount,
				unsigned int		cMin,
				unsigned int		cMax )
    {
    int		cMed;

    int		lt= 0;
    int		eq= 0;
    int		gt= 0;

    cMax++;

    cMed= ( cMin+ cMax )/ 2;
    for (;;)
	{
	int			i;
	const HistogramEntry *	he;

	lt= 0; eq= 0; gt= 0;

	he= histogram;
	for ( i= 0; i < colorCount; he++, i++ )
	    {
	    if  ( he->heRed > cMed )
		{ gt += he->heCount; continue; }
	    if  ( he->heRed < cMed )
		{ lt += he->heCount; continue; }

	    eq += he->heCount; continue;
	    }

	if  ( lt+ eq < pixelCount/ 2 )
	    {
	    cMin= cMed;
	    cMed= ( cMin+ cMax )/ 2;
	    continue;
	    }

	if  ( eq+ gt < pixelCount/ 2 )
	    {
	    cMax= cMed;
	    cMed= ( cMin+ cMax )/ 2;
	    continue;
	    }

	break;
	}

    if  ( lt+ eq < eq+ gt )
	{ pixelCount= lt+ eq;		}
    else{ cMed--; pixelCount= lt;	}

    lt= 0; gt= colorCount- 1;
    while( gt > lt )
	{
	while( gt > lt && histogram[lt].heColor.rgb8Red <= cMed )
	    { lt++;	}
	while( gt > lt && histogram[gt].heColor.rgb8Red >  cMed )
	    { gt--;	}

	if  ( gt > lt )
	    {
	    HistogramEntry	swap;

	    swap= histogram[lt];
	    histogram[lt]= histogram[gt];
	    histogram[gt]= swap;
	    }
	}

    while( lt < colorCount && histogram[lt].heColor.rgb8Red <= cMed )
	{ lt++;	}

    *pPixelCount= pixelCount; *pColorCount= lt; return cMed;
    }

static int bmHistGreenMedian(	int *			pPixelCount,
				int *			pColorCount,
				int			pixelCount,
				HistogramEntry *	histogram,
				int			colorCount,
				unsigned int		cMin,
				unsigned int		cMax )
    {
    int		cMed;

    int		lt= 0;
    int		eq= 0;
    int		gt= 0;

    cMax++;

    cMed= ( cMin+ cMax )/ 2;
    for (;;)
	{
	int			i;
	const HistogramEntry *	he;

	lt= 0; eq= 0; gt= 0;

	he= histogram;
	for ( i= 0; i < colorCount; he++, i++ )
	    {
	    if  ( he->heGreen > cMed )
		{ gt += he->heCount; continue; }
	    if  ( he->heGreen < cMed )
		{ lt += he->heCount; continue; }

	    eq += he->heCount; continue;
	    }

	if  ( lt+ eq < pixelCount/ 2 )
	    {
	    cMin= cMed;
	    cMed= ( cMin+ cMax )/ 2;
	    continue;
	    }

	if  ( eq+ gt < pixelCount/ 2 )
	    {
	    cMax= cMed;
	    cMed= ( cMin+ cMax )/ 2;
	    continue;
	    }

	break;
	}

    if  ( lt+ eq < eq+ gt )
	{ pixelCount= lt+ eq;		}
    else{ cMed--; pixelCount= lt;	}

    lt= 0; gt= colorCount- 1;
    while( gt > lt )
	{
	while( gt > lt && histogram[lt].heColor.rgb8Green <= cMed )
	    { lt++;	}
	while( gt > lt && histogram[gt].heColor.rgb8Green >  cMed )
	    { gt--;	}

	if  ( gt > lt )
	    {
	    HistogramEntry	swap;

	    swap= histogram[lt];
	    histogram[lt]= histogram[gt];
	    histogram[gt]= swap;
	    }
	}

    while( lt < colorCount && histogram[lt].heColor.rgb8Green <= cMed )
	{ lt++;	}

    *pPixelCount= pixelCount; *pColorCount= lt; return cMed;
    }

static int bmHistBlueMedian(	int *			pPixelCount,
				int *			pColorCount,
				int			pixelCount,
				HistogramEntry *	histogram,
				int			colorCount,
				unsigned int		cMin,
				unsigned int		cMax )
    {
    int		cMed;

    int		lt= 0;
    int		eq= 0;
    int		gt= 0;

    cMax++;


    cMed= ( cMin+ cMax )/ 2;
    for (;;)
	{
	int			i;
	const HistogramEntry *	he;

	lt= 0; eq= 0; gt= 0;

	he= histogram;
	for ( i= 0; i < colorCount; he++, i++ )
	    {
	    if  ( he->heBlue > cMed )
		{ gt += he->heCount; continue; }
	    if  ( he->heBlue < cMed )
		{ lt += he->heCount; continue; }

	    eq += he->heCount; continue;
	    }

	if  ( lt+ eq < pixelCount/ 2 )
	    {
	    cMin= cMed;
	    cMed= ( cMin+ cMax )/ 2;
	    continue;
	    }

	if  ( eq+ gt < pixelCount/ 2 )
	    {
	    cMax= cMed;
	    cMed= ( cMin+ cMax )/ 2;
	    continue;
	    }

	break;
	}

    if  ( lt+ eq < eq+ gt )
	{ pixelCount= lt+ eq;		}
    else{ cMed--; pixelCount= lt;	}

    lt= 0; gt= colorCount- 1;
    while( gt > lt )
	{
	while( gt > lt && histogram[lt].heColor.rgb8Blue <= cMed )
	    { lt++;	}
	while( gt > lt && histogram[gt].heColor.rgb8Blue >  cMed )
	    { gt--;	}

	if  ( gt > lt )
	    {
	    HistogramEntry	swap;

	    swap= histogram[lt];
	    histogram[lt]= histogram[gt];
	    histogram[gt]= swap;
	    }
	}

    while( lt < colorCount && histogram[lt].heColor.rgb8Blue <= cMed )
	{ lt++;	}

    *pPixelCount= pixelCount; *pColorCount= lt; return cMed;
    }

/************************************************************************/
/*									*/
/*  Determine the range of the colors in a box.				*/
/*									*/
/************************************************************************/

static void bmDelimitColorBox(		ColorBox *		cb,
					const HistogramEntry *	histogram )
    {
    const HistogramEntry *	he;
    int				i;

    if  ( cb->cbColorCount == 0 )
	{ LDEB(cb->cbColorCount); return;	}

    he= histogram+ cb->cbHistogramOffset;
    cb->cbRMin= cb->cbRMax= he->heRed;
    cb->cbGMin= cb->cbGMax= he->heGreen;
    cb->cbBMin= cb->cbBMax= he->heBlue;

    he++;

    for ( i= 1; i < cb->cbColorCount; he++, i++ )
	{
	if  ( cb->cbRMin > he->heRed )
	    { cb->cbRMin=  he->heRed;	}
	if  ( cb->cbRMax < he->heRed )
	    { cb->cbRMax=  he->heRed;	}

	if  ( cb->cbGMin > he->heGreen )
	    { cb->cbGMin=  he->heGreen; }
	if  ( cb->cbGMax < he->heGreen )
	    { cb->cbGMax=  he->heGreen; }

	if  ( cb->cbBMin > he->heBlue )
	    { cb->cbBMin=  he->heBlue; }
	if  ( cb->cbBMax < he->heBlue )
	    { cb->cbBMax=  he->heBlue; }
	}

    return;
    }

/************************************************************************/
/*									*/
/*  Find the median for splitting a box.				*/
/*									*/
/************************************************************************/

static void bmSplitBox(	HistogramEntry *	histogram,
			const ColorBox *	cbParent,
			ColorBox *		cbLow,
			ColorBox *		cbHigh,
			int *			pLeftMaximum,
			int *			pComponent )
    {
    int				rDif= cbParent->cbRMax- cbParent->cbRMin;
    int				gDif= cbParent->cbGMax- cbParent->cbGMin;
    int				bDif= cbParent->cbBMax- cbParent->cbBMin;

    histogram += cbParent->cbHistogramOffset;

    *cbLow= *cbParent;
    *cbHigh= *cbParent;

    if  ( 77* rDif > 150* gDif && 77* rDif >  29* bDif )
	{
	cbLow->cbRMax= bmHistRedMedian( &(cbLow->cbPixelCount),
				    &(cbLow->cbColorCount),
				    cbParent->cbPixelCount, histogram,
				    cbParent->cbColorCount,
				    cbParent->cbRMin, cbParent->cbRMax );

	cbHigh->cbRMin= cbLow->cbRMax+ 1;
	cbHigh->cbColorCount= cbParent->cbColorCount- cbLow->cbColorCount;
	cbHigh->cbPixelCount= cbParent->cbPixelCount- cbLow->cbPixelCount;
	cbHigh->cbHistogramOffset= cbParent->cbHistogramOffset+
							cbLow->cbColorCount;
	*pLeftMaximum= cbLow->cbRMax;
	*pComponent= CN_RED;

#	if  LOG_CUTS
	appDebug( "CUT RED   at %3d..%3d..%3d "
			    "%4d+ %4d= %4dC %6d+ %6d= %6dP\n",
			    cbParent->cbRMin, cbLow->cbRMax, cbParent->cbRMax,
			    cbLow->cbColorCount, cbHigh->cbColorCount,
			    cbParent->cbColorCount,
			    cbLow->cbPixelCount, cbHigh->cbPixelCount,
			    cbParent->cbPixelCount );
#	endif
	}
    else{
	if  ( 150* gDif > 29* bDif )
	    {
	    cbLow->cbGMax= bmHistGreenMedian( &(cbLow->cbPixelCount),
				    &(cbLow->cbColorCount),
				    cbParent->cbPixelCount, histogram,
				    cbParent->cbColorCount,
				    cbParent->cbGMin, cbParent->cbGMax );
	    cbHigh->cbGMin= cbLow->cbGMax+ 1;
	    cbHigh->cbColorCount= cbParent->cbColorCount- cbLow->cbColorCount;
	    cbHigh->cbPixelCount= cbParent->cbPixelCount- cbLow->cbPixelCount;
	    cbHigh->cbHistogramOffset= cbParent->cbHistogramOffset+
							cbLow->cbColorCount;
	    *pLeftMaximum= cbLow->cbGMax;
	    *pComponent= CN_GREEN;

#	    if  LOG_CUTS
	    appDebug( "CUT GREEN at %3d..%3d..%3d "
			    "%4d+ %4d= %4dC %6d+ %6d= %6dP\n",
			    cbParent->cbGMin, cbLow->cbGMax, cbParent->cbGMax,
			    cbLow->cbColorCount, cbHigh->cbColorCount,
			    cbParent->cbColorCount,
			    cbLow->cbPixelCount, cbHigh->cbPixelCount,
			    cbParent->cbPixelCount );
#	    endif
	    }
	else{
	    cbLow->cbBMax= bmHistBlueMedian( &(cbLow->cbPixelCount),
				    &(cbLow->cbColorCount),
				    cbParent->cbPixelCount, histogram,
				    cbParent->cbColorCount,
				    cbParent->cbBMin, cbParent->cbBMax );

	    cbHigh->cbBMin= cbLow->cbBMax+ 1;
	    cbHigh->cbColorCount= cbParent->cbColorCount- cbLow->cbColorCount;
	    cbHigh->cbPixelCount= cbParent->cbPixelCount- cbLow->cbPixelCount;
	    cbHigh->cbHistogramOffset= cbParent->cbHistogramOffset+
							cbLow->cbColorCount;
	    *pLeftMaximum= cbLow->cbBMax;
	    *pComponent= CN_BLUE;

#	    if  LOG_CUTS
	    appDebug( "CUT BLUE  at %3d..%3d..%3d "
			    "%4d+ %4d= %4dC %6d+ %6d= %6dP\n",
			    cbParent->cbBMin, cbLow->cbBMax, cbParent->cbBMax,
			    cbLow->cbColorCount, cbHigh->cbColorCount,
			    cbParent->cbColorCount,
			    cbLow->cbPixelCount, cbHigh->cbPixelCount,
			    cbParent->cbPixelCount );
#	    endif
	    }
	}

    return;
    }

/************************************************************************/
/*									*/
/*  Find a collection of colors using 'median cut'.			*/
/*									*/
/************************************************************************/

static int bmFindCuts(		ColorBox *		boxes,
				CutNode *		nodes,
				HistogramEntry *	histo,
				int			colorCount,
				int			pixelCount,
				int			maxcolors )
    {
    int				i;
    int				boxCount;
    int				nodeCount= 0;

    boxes[0].cbHistogramOffset= 0;
    boxes[0].cbColorCount= colorCount;
    boxes[0].cbPixelCount= pixelCount;
    boxes[0].cbCutNumber= 0;
    boxCount= 1;

    nodes[0].cnComponent= CN_LEAF;
    nodes[0].cnLeftMaximum= 0;		/*  Irrelevant !	*/
    nodes[0].cnLeft= 0;		/*  Both to this color.	*/
    nodes[0].cnRight= 0;		/*  Both to this color.	*/
    nodeCount= 1;

    while( boxCount < maxcolors )
	{
	int		biggestBox;
	int		leftMaximum;
	int		cutComponent;

	ColorBox	cbSplit;

	for ( i= 0; i < boxCount; i++ )
	    {
	    if  ( boxes[i].cbColorCount > 1 )
		{ break;	}
	    }
	if  ( i >= boxCount )
	    { /* LLDEB(i,boxCount); */ break;	}
	biggestBox= i;
	for ( i= biggestBox+ 1; i < boxCount; i++ )
	    {
	    if  ( boxes[i].cbColorCount > 1				&&
		  boxes[i].cbPixelCount > boxes[biggestBox].cbPixelCount )
		{ biggestBox= i;	}
	    }

	bmDelimitColorBox( boxes+ biggestBox, histo );

	cbSplit= boxes[biggestBox];
	bmSplitBox( histo,
				&cbSplit, boxes+ biggestBox, boxes+ boxCount,
				&leftMaximum, &cutComponent );

	/****************/
	/*  Cut Nodes	*/
	/****************/
	i= cbSplit.cbCutNumber;
	nodes[i].cnComponent= cutComponent;
	nodes[i].cnLeftMaximum= leftMaximum;
	nodes[i].cnLeft= nodeCount;
	nodes[i].cnRight= nodeCount+ 1;

	nodes[nodeCount].cnComponent= CN_LEAF;
	nodes[nodeCount].cnLeftMaximum= 0;
	nodes[nodeCount].cnLeft= biggestBox;
	nodes[nodeCount].cnRight= biggestBox;

	nodes[nodeCount+1].cnComponent= CN_LEAF;
	nodes[nodeCount+1].cnLeftMaximum= 0;
	nodes[nodeCount+1].cnLeft= boxCount;
	nodes[nodeCount+1].cnRight= boxCount;

	/****************/
	/*  Boxes	*/
	/****************/
	boxes[biggestBox].cbCutNumber= nodeCount;
	boxes[boxCount].cbCutNumber= nodeCount+ 1;

	nodeCount += 2; boxCount++;
	}

    return boxCount;
    }

static int bmFindColors(	ColorReducer *		cr,
				int			colorCount,
				int			pixelCount,
				int			maxcolors )
    {
    ColorBox *			boxes;
    const ColorBox *		cb;
    int				i;
    int				j;
    int				boxCount;

    boxes= (ColorBox *)malloc( maxcolors* sizeof(ColorBox) );
    if  ( ! boxes )
	{ LLDEB(maxcolors,boxes); return -1; }

    boxCount= bmFindCuts( boxes, cr->crCutNodes, cr->crHistogram,
					colorCount, pixelCount, maxcolors );

    cb= boxes;
    for ( i= 0; i < boxCount; cb++, i++ )
	{
	long			sR= 0;
	long			sG= 0;
	long			sB= 0;
	long			N= 0;

	HistogramEntry *	he= cr->crHistogram+ cb->cbHistogramOffset;

	for ( j= 0; j < boxes[i].cbColorCount; j++, he++ )
	    {
	    sR += he->heCount* he->heRed;
	    sG += he->heCount* he->heGreen;
	    sB += he->heCount* he->heBlue;
	    N  += he->heCount;
	    }

	if  ( N == 0 )
	    {
	    LDEB(N);
	    cr->crPalette->cpColors[i].rgb8Red= 0;
	    cr->crPalette->cpColors[i].rgb8Green= 0;
	    cr->crPalette->cpColors[i].rgb8Blue= 0;
	    }
	else{
	    cr->crPalette->cpColors[i].rgb8Red= sR/ N;
	    cr->crPalette->cpColors[i].rgb8Green= sG/ N;
	    cr->crPalette->cpColors[i].rgb8Blue= sB/ N;
	    }

#	if LOG_PALETTE
	appDebug( "BOX %3d: "
			"R %3d..%3d..%3d "
			"G %3d..%3d..%3d "
			"B %3d..%3d..%3d "
			"%4dC %6ldP\n",

			i,
			cb->cbRMin, cr->crPalette[i].rgb8Red, cb->cbRMax,
			cb->cbGMin, cr->crPalette[i].rgb8Green, cb->cbGMax,
			cb->cbBMin, cr->crPalette[i].rgb8Blue, cb->cbBMax,
			boxes[i].cbColorCount, N );
#	endif
	}

    free( boxes );

    return boxCount;
    }

/************************************************************************/
/*									*/
/*  Various translation routines.					*/
/*									*/
/************************************************************************/

static int bmReduceAllocateColorCut(	AllocatorColor *	ac,
					ColorAllocator *	ca,
					unsigned int		r,
					unsigned int		g,
					unsigned int		b )
    {
    ColorReducer *	cr= (ColorReducer *)ca->caSystemPrivate;
    const CutNode *	cn= cr->crCutNodes;
    int			i;

    i= 0;
    for (;;)
	{
	switch( cn[i].cnComponent )
	    {
	    case CN_RED:
		if  ( r <= cn[i].cnLeftMaximum )
		    { i= cn[i].cnLeft;	}
		else{ i= cn[i].cnRight;	}
		continue;

	    case CN_GREEN:
		if  ( g <= cn[i].cnLeftMaximum )
		    { i= cn[i].cnLeft;	}
		else{ i= cn[i].cnRight;	}
		continue;

	    case CN_BLUE:
		if  ( b <= cn[i].cnLeftMaximum )
		    { i= cn[i].cnLeft;	}
		else{ i= cn[i].cnRight;	}
		continue;

	    case CN_LEAF:
		i= cn[i].cnLeft;

		ac->acRed= 257* cr->crPalette->cpColors[i].rgb8Red;
		ac->acGreen= 257* cr->crPalette->cpColors[i].rgb8Green;
		ac->acBlue= 257* cr->crPalette->cpColors[i].rgb8Blue;
		ac->acColorNumber= i;
		return 0;

	    default:
		LDEB(cn[i].cnComponent); return -1;
	    }
	}
    }

static int bmReduceAllocateColorHash(	AllocatorColor *	ac,
					ColorAllocator *	ca,
					unsigned int		r,
					unsigned int		g,
					unsigned int		b )
    {
    ColorReducer *	cr= (ColorReducer *)ca->caSystemPrivate;
    int			hash;
    HashBucket *	hashBucket;
    int			i;

    hash= ppm_hash( r, g, b );
    hashBucket= cr->crHashTable[hash];
    while( hashBucket )
	{
	if  ( hashBucket->hbRed == r	&&
	      hashBucket->hbGreen == g	&&
	      hashBucket->hbBlue == b	)
	    { break;	}

	hashBucket= hashBucket->hbNext;
	}

    if  ( ! hashBucket )
	{ XDEB(hashBucket); i= 0;	}
    else{ i= hashBucket->hbNumber;	}

    ac->acRed= 257* cr->crPalette->cpColors[i].rgb8Red;
    ac->acGreen= 257* cr->crPalette->cpColors[i].rgb8Green;
    ac->acBlue= 257* cr->crPalette->cpColors[i].rgb8Blue;
    ac->acColorNumber= i;

    return 0;
    }

static void bmReduceCleanupAllocator(	ColorAllocator *	ca )
    {
    ColorReducer *	cr= (ColorReducer *)ca->caSystemPrivate;

    if  ( cr )
	{
	bmCleanColorReducer( cr );
	free( cr );
	}

    return;
    }

/************************************************************************/
/*									*/
/*  Hash the colors in a 24 bit image. Do not return more than a	*/
/*  given number of colors.						*/
/*									*/
/*  1)  Make a hash table for the colors.				*/
/*									*/
/************************************************************************/

static int bmHashColorsRGB8(	HashBucket ***		pHashTable,
				int *			pColorCount,
				int			pixelsHigh,
				int			pixelsWide,
				int			bytesPerRow,
				int			hasAlpha,
				const unsigned char *	bufIn,
				int			maxcolors,
				unsigned int		mask )
    {
    unsigned int		r;
    unsigned int		g;
    unsigned int		b;

    int				row;
    int				col;

    HashBucket **		hashTable;
    HashBucket *		hashBucket;
    int				hash;

    int				colorCount= 0;

    const unsigned char *	from;

    /*  1  */
    hashTable= (HashBucket **)malloc( HASH_SIZE* sizeof(HashBucket *) );
    if  ( ! hashTable )
	{ XDEB(hashTable); return -1;	}
    for ( row= 0; row < HASH_SIZE; row++ )
	{ hashTable[row]= (HashBucket *)0;	}

    for ( row= 0; row < pixelsHigh; row++ )
	{
	from= bufIn+ row* bytesPerRow;

	for ( col= 0; col < pixelsWide; col++ )
	    {
	    r= *(from++) & mask; g= *(from++) & mask; b= *(from++) & mask;
	    hash= ppm_hash( r, g, b );

	    if  ( hasAlpha )
		{ from++;	}

	    if  ( hash < 0 || hash >= HASH_SIZE )
		{ LLDEB(hash,HASH_SIZE); return -1; }

	    hashBucket= hashTable[hash];
	    while( hashBucket )
		{
		if  ( hashBucket->hbRed == r	&&
		      hashBucket->hbGreen == g	&&
		      hashBucket->hbBlue == b	)
		    { break;	}
		hashBucket= hashBucket->hbNext;
		}
	    if  ( hashBucket )
		{ hashBucket->hbCount++; continue;	}

	    if  ( bmInsertHash( hashTable, hash, colorCount++, r, g, b ) )
		{ LDEB(1); bmFreeColorHash( hashTable ); return -1; }

	    if  ( colorCount > maxcolors )
		{ bmFreeColorHash( hashTable ); return 1; }
	    }
	}

    *pColorCount= colorCount;
    *pHashTable= hashTable;

    return 0;
    }

/************************************************************************/
/*									*/
/*  Convert a color hash table to a histogram.				*/
/*									*/
/************************************************************************/

static int bmHashToHistogram(	ColorReducer *		cr,
				int			colorCount )
    {
    HashBucket *		hashBucket;

    int				row;
    int				i;

    row= 0;
    for ( i= 0; i < HASH_SIZE; i++ )
	{
	hashBucket= cr->crHashTable[i];

	while( hashBucket )
	    {
	    cr->crHistogram[row++]= hashBucket->hbHistogramEntry;
	    hashBucket= hashBucket->hbNext;
	    }
	}

    return 0;
    }

static void bmHashToPalette(	ColorReducer *		cr )
    {
    HashBucket *		hashBucket;

    int				i;

    for ( i= 0; i < HASH_SIZE; i++ )
	{
	hashBucket= cr->crHashTable[i];

	while( hashBucket )
	    {
	    cr->crPalette->cpColors[hashBucket->hbNumber]=
					hashBucket->hbHistogramEntry.heColor;

	    hashBucket= hashBucket->hbNext;
	    }
	}

    return;
    }

/************************************************************************/
/*									*/
/*  Reduce the number of colors in an image to at most 'maxcolors'.	*/
/*									*/
/*  0)  Allocate the palette.						*/
/*  1)  Make a hash table for the colors.				*/
/*  2)  Store and count all the colors in the hash table.		*/
/*  3)  If the number of colors is below the maximum, fill the palette	*/
/*	with the colors in the hash table.				*/
/*  4)  Else translate the hash table to a histogram.			*/

/*  4)  Allocate memory for the palette and for the tree that will be	*/
/*	used to classify the colors.					*/
/*  5)  Classify colors: Yields a palette and a tree.			*/
/*  6)  Allocate and initialise output.					*/
/*  7)  Translate to representative of box.				*/
/*									*/
/************************************************************************/

int bmColorReduce(	RasterImage *			riOut,
			const RasterImage *		riIn,
			int				maxcolors )
    {
    const BitmapDescription *	bdIn= &(riIn->riDescription);
    int				rval= 0;

    int				row;
    int				i;

    unsigned char		mask= 0xff;

    ColorAllocator		ca;
    ColorReducer *		cr;

    int				colorCount= 0;
    int				colorsFound;

    int				bitsPerPixel;

    int				bitmapUnit= 0;
    int				swapBitmapBytes= 0;
    int				swapBitmapBits= 0;
    const int			dither= 0;

    RasterImage			ri;

    bmInitColorAllocator( &ca );
    bmInitRasterImage( &ri );

    cr= (ColorReducer *)malloc( sizeof( ColorReducer ) );
    if  ( ! cr )
	{ XDEB(cr); rval= -1; goto ready;	}

    bmInitColorReducer( cr );

    ca.caSystemPrivate= (void *)cr;
    ca.caAllocationType= CA_ALLOCATOR;
    ca.caSystemCleanup= bmReduceCleanupAllocator;

    if  ( maxcolors > 256 )
	{ LDEB(maxcolors); rval= -1; goto ready;	}

    if  ( maxcolors <= 16 )
	{ bitsPerPixel= 4;	}
    else{ bitsPerPixel= 8;	}

    /*  0  */
    if  ( utilPaletteSetCount( &(ri.riDescription.bdPalette), maxcolors ) )
	{ LDEB(maxcolors); rval= -1; goto ready;	}

    cr->crPalette= &(ri.riDescription.bdPalette);

    /*  1, 2  */
    switch( bdIn->bdBitsPerPixel )
	{
	case 24:
	case 32:
	    for ( i= 0; i < 8; i++ )
		{
		mask= ~( 0xff >> ( 8- i ) );

		row= bmHashColorsRGB8( &(cr->crHashTable), &colorCount,
				bdIn->bdPixelsHigh, bdIn->bdPixelsWide,
				bdIn->bdBytesPerRow, bdIn->bdHasAlpha,
				riIn->riBytes, 32768, mask );
		if  ( row < 0 )
		    { LDEB(row); rval= -1; goto ready;	}
		if  ( row == 0 )
		    { break;				}
		}
	    break;

	default:
	    LDEB(bdIn->bdBitsPerPixel); rval= -1; goto ready;
	}

    /*  3  */
    if  ( mask == 0xff && colorCount <= maxcolors )
	{
	bmHashToPalette( cr );

	colorsFound= colorCount;
	}
    else{
	/*  4  */
	if  ( bmAllocateHistogram( cr, colorCount ) )
	    { LDEB(colorCount); rval= -1; goto ready;	}

	if  ( bmHashToHistogram( cr, colorCount ) )
	    { LDEB(colorCount); rval= -1; goto ready;	}

	if  ( bmAllocateCutNodes( cr, maxcolors ) )
	    { LDEB(maxcolors); rval= -1; goto ready;	}

	/*  5  */
	colorsFound= bmFindColors( cr, colorCount,
			bdIn->bdPixelsHigh* bdIn->bdPixelsWide, maxcolors );

	if  ( colorsFound < 1 )
	    { LLDEB(maxcolors,colorsFound); rval= -1; goto ready; }
	}

    /*  6  */
    ri.riDescription.bdPixelsWide= bdIn->bdPixelsWide;
    ri.riDescription.bdPixelsHigh= bdIn->bdPixelsHigh;

    ri.riDescription.bdBitsPerSample= bdIn->bdBitsPerSample;
    ri.riDescription.bdSamplesPerPixel= 3;
    ri.riDescription.bdBitsPerPixel= bitsPerPixel;

    ri.riDescription.bdXResolution= bdIn->bdXResolution;
    ri.riDescription.bdYResolution= bdIn->bdYResolution;
    ri.riDescription.bdUnit= bdIn->bdUnit;

    ri.riDescription.bdColorEncoding= BMcoRGB8PALETTE;
    ri.riDescription.bdPalette.cpColorCount= colorsFound;

    ri.riDescription.bdHasAlpha= 0;

    if  ( bmCalculateSizes( &(ri.riDescription) ) )
	{ LDEB(1); rval= -1; goto ready;	}

    if  ( bmAllocateBuffer( &ri ) )
	{ LLDEB(ri.riDescription.bdBufferLength,ri.riBytes); rval= -1; goto ready; }

    /*  7  */
    if  ( cr->crCutNodes )
	{ ca.caSystemAllocator= bmReduceAllocateColorCut;	}
    else{ ca.caSystemAllocator= bmReduceAllocateColorHash;	}

    if  ( bmFillImage( &ca, bitmapUnit, swapBitmapBytes, swapBitmapBits,
			dither, ri.riBytes, &(ri.riDescription),
			riIn, (const DocumentRectangle *)0 ) )
	{ LDEB(1); rval= -1; goto ready;	}

    /* steal */
    *riOut= ri; bmInitRasterImage( &ri );

  ready:

    bmCleanRasterImage( &ri );
    bmCleanColorAllocator( &ca ); /* also frees cr */

    return rval;
    }

/************************************************************************/
/*									*/
/*  Make a color allocator to be used with a certain palette.		*/
/*									*/
/************************************************************************/

int bmSetColorAllocatorForPaletteImage(	ColorAllocator *		ca,
					const BitmapDescription *	bd )
    {
    int				rval= 0;

    int				i;
    ColorReducer *		cr= (ColorReducer *)0;
    ColorBox *			boxes= (ColorBox *)0;
    int *			map= (int *)0;
    int				boxCount;

    const ColorPalette *	cp= &(bd->bdPalette);

    if  ( bd->bdColorEncoding != BMcoRGB8PALETTE )
	{ LLDEB(bd->bdColorEncoding,BMcoRGB8PALETTE); rval= -1; goto ready; }

    cr= (ColorReducer *)malloc( sizeof( ColorReducer ) );
    if  ( ! cr )
	{ XDEB(cr); rval= -1; goto ready;	}

    bmInitColorReducer( cr );

    ca->caSystemPrivate= (void *)cr;
    ca->caAllocationType= CA_ALLOCATOR;
    ca->caSystemCleanup= bmReduceCleanupAllocator;

    cr->crPalette= &(bd->bdPalette);

    if  ( bmAllocateCutNodes( cr, cp->cpColorCount ) )
	{ LDEB(cp->cpColorCount); rval= -1; goto ready;	}

    if  ( bmAllocateHistogram( cr, cp->cpColorCount ) )
	{ LDEB(cp->cpColorCount); rval= -1; goto ready;	}

    boxes= (ColorBox *)malloc( cp->cpColorCount* sizeof(ColorBox) );
    if  ( ! boxes )
	{ LLDEB(cp->cpColorCount,boxes); return -1; }

    map= (int *)malloc( cp->cpColorCount* sizeof(int) );
    if  ( ! map )
	{ LLDEB(cp->cpColorCount,map); return -1; }

    for ( i= 0; i < cp->cpColorCount; i++ )
	{
	cr->crHistogram[i].heColor= cp->cpColors[i];
	cr->crHistogram[i].heCount= 1;

	map[i]= -1;
	}

    boxCount= bmFindCuts( boxes, cr->crCutNodes, cr->crHistogram,
						cp->cpColorCount,
						cp->cpColorCount,
						cp->cpColorCount );

    if  ( boxCount != cp->cpColorCount )
	{ LLDEB(boxCount,cp->cpColorCount); rval= -1; goto ready; }

    ca->caSystemAllocator= bmReduceAllocateColorCut;

    for ( i= 0; i < cp->cpColorCount; i++ )
	{
	AllocatorColor	ac;

	bmReduceAllocateColorCut( &ac, ca,
				    cp->cpColors[i].rgb8Red,
				    cp->cpColors[i].rgb8Green,
				    cp->cpColors[i].rgb8Blue );

	if  ( /*ac.acColorNumber < 0				||*/
	      ac.acColorNumber >= cp->cpColorCount	)
	    { LLDEB(i,ac.acColorNumber);	}

	map[ac.acColorNumber]= i;
	}

    for ( i= 0; i < 2* cp->cpColorCount; i++ )
	{
	if  ( cr->crCutNodes[i].cnComponent != CN_LEAF )
	    { continue;	}

	cr->crCutNodes[i].cnLeft=map[cr->crCutNodes[i].cnLeft];
	}

#   if 0
    for ( i= 0; i < cp->cpColorCount; i++ )
	{
	AllocatorColor	ac;

	bmReduceAllocateColorCut( &ac, ca,
				    cp->cpColors[i].rgb8Red,
				    cp->cpColors[i].rgb8Green,
				    cp->cpColors[i].rgb8Blue );

	if  ( ac.acColorNumber != i )
	    {
	    LLDEB(i,ac.acColorNumber);
	    LDEB(cp->cpColors[i].rgb8Red);
	    LDEB(cp->cpColors[i].rgb8Green);
	    LDEB(cp->cpColors[i].rgb8Blue);
	    }
	}
#   endif

  ready:

    if  ( boxes )
	{ free( boxes );	}
    if  ( map )
	{ free( map );	}

    return rval;
    }

