/* -*-c-*- */
/* Copyright (C) 1993, Robert Nation
 * Copyright (C) 2002  Olivier Chapuis */
/* 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/* ---------------------------- included header files ---------------------- */

#include "config.h"

#include <stdio.h>
#include <signal.h>
#include <ctype.h>
#include <math.h>

#include <X11/Xlib.h>
#include <X11/Xmd.h>

#include "fvwmlib.h"
#include "envvar.h"
#include "Parse.h"
#include "ftime.h"
#include "PictureBase.h"
#include "PictureUtils.h"
#include "PictureDitherMatrice.h"

/* ---------------------------- local definitions and macro ----------------- */

#if 0
/* dv: unused */
/* form alloc_in_cmap from the xpm lib */
#define XPM_DIST(r1,g1,b1,r2,g2,b2) (long)\
                          (3*(abs((long)r1-(long)r2) + \
			      abs((long)g1-(long)g2) + \
			      abs((long)b1-(long)b2)) + \
			   abs((long)r1 + (long)g1 + (long)b1 - \
			       ((long)r2 +  (long)g2 + (long)b2)))
#define XPM_COLOR_CLOSENESS 40000
#endif

#define SQUARE(X) ((X)*(X))

#define TRUE_DIST(r1,g1,b1,r2,g2,b2) (long)\
                   (SQUARE((long)((r1 - r2)>>8)) \
		+   SQUARE((long)((g1 - g2)>>8)) \
                +   SQUARE((long)((b1 - b2)>>8)))

#define FAST_DIST(r1,g1,b1,r2,g2,b2) (long)\
                   (abs((long)(r1 - r2)) \
		+   abs((long)(g1 - g2)) \
                +   abs((long)(b1 - b2)))

#define FVWM_DIST(r1,g1,b1,r2,g2,b2) \
                   (abs(r1 - r2) + abs(g1 - g2) + abs(b1 - b2) \
                    + 2*abs(abs(r1-g1) + abs(g1-b1) + abs(r1-b1) \
                            - abs(r2-g2) - abs(g2-b2) - abs(r2-b2)))

#define USED_DIST(r1,g1,b1,r2,g2,b2) FVWM_DIST(r1,g1,b1,r2,g2,b2)

#define PICTURE_COLOR_CLOSENESS USED_DIST(3,3,3,0,0,0)

#define PICTURE_PAllocTable         1000000
#define PICTURE_PUseDynamicColors   100000
#define PICTURE_PStrictColorLimit   10000
#define PICTURE_use_named           1000
#define PICTURE_TABLETYPE_LENGHT    7

/* humm ... dither is probably borken with gamma correction. Anyway I do
 * do think that using gamma correction for the colors cubes is a good
 * idea */
#define USE_GAMMA_CORECTION 0
/* 2.2 is recommanded by the Poynon colors FAQ, some others suggest 1.5 and 2
 * Use float constants!*/
#define COLOR_GAMMA 1.5
#define GREY_GAMMA  2.0

/* ---------------------------- imports ------------------------------------ */

/* ---------------------------- included code files ------------------------ */

/* ---------------------------- local types -------------------------------- */

typedef struct
{
	XColor color;               /* rgb color info */
	unsigned long alloc_count;  /* nbr of allocation */
} PColor;

typedef struct
{
	/*
	 * info for colors table (depth <= 8)
	 */
	/* color cube used */
	short nr;
	short ng;
	short nb;
	short ngrey;
	/* grey palette def, nbr of grey = 2^grey_bits */
	short grey_bits;
	/* color cube used for dithering with the named table */
	short d_nr;
	short d_ng;
	short d_nb;
	short d_ngrey_bits;
	/* do we found a pre-allocated pallet ? */
	Bool pre_allocated_pallet;
	/* info for depth > 8 */
	int red_shift;
	int green_shift;
	int blue_shift;
	int red_prec;
	int green_prec;
	int blue_prec;
	/* for dithering in depth 15 and 16 */
	unsigned short *red_dither;
	unsigned short *green_dither;
	unsigned short *blue_dither;
	/* colors allocation function */
	int (*alloc_color)(Display *dpy, Colormap cmap, XColor *c);
	int (*alloc_color_no_limit)(Display *dpy, Colormap cmap, XColor *c);
	int (*alloc_color_dither)(
		Display *dpy, Colormap cmap, XColor *c, int x, int y);
	void (*free_colors)(
		Display *dpy, Colormap cmap, Pixel *pixels, int n,
		unsigned long planes);
	void (*free_colors_no_limit)(
		Display *dpy, Colormap cmap, Pixel *pixels, int n,
		unsigned long planes);
} PColorsInfo;

typedef struct {
	int cols_index;
	long closeness;
} CloseColor;

/* ---------------------------- forward declarations ----------------------- */

/* ---------------------------- local variables ---------------------------- */

static int PColorLimit = 0;
static PColor *Pct = NULL;
static PColor *Pac = NULL;
static short *PMappingTable = NULL;
static short *PDitherMappingTable = NULL;
static Bool PStrictColorLimit = 0;
static Bool PAllocTable = 0;
static PColorsInfo Pcsi = {
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL,
	NULL, NULL, NULL, NULL};

/* ---------------------------- exported variables (globals) --------------- */

/* ---------------------------- local functions ---------------------------- */

/*
 * get shift and prec from a mask
 */
static
void decompose_mask(
	unsigned long mask, int *shift, int *prec)
{
	*shift = 0;
	*prec = 0;

	while (!(mask & 0x1))
	{
		(*shift)++;
		mask >>= 1;
	}

	while (mask & 0x1)
	{
		(*prec)++;
		mask >>= 1;
	}
}

/*
 * color allocation in the colormap. strongly inspired by SetCloseColor from
 * the Xpm library (depth <= 8)
 */

static int
closeness_cmp(const void *a, const void *b)
{
    CloseColor *x = (CloseColor *) a, *y = (CloseColor *) b;

    /* cast to int as qsort requires */
    return (int) (x->closeness - y->closeness);
}

static
int alloc_color_in_cmap(XColor *c, Bool force)
{
	static XColor colors[256];
	CloseColor closenesses[256];
	XColor tmp;
	int i,j;
	int map_entries = (Pvisual->class == DirectColor)?
		(1 << Pdepth) : Pvisual->map_entries;
	time_t current_time;
	time_t last_time = 0;

	map_entries = (map_entries > 256)? 256:map_entries;
	current_time = time(NULL);
	if (current_time - last_time >= 2 || force)
	{
		last_time = current_time;
		for (i = 0; i < map_entries; i++)
		{
			colors[i].pixel = i;
		}
		XQueryColors(Pdpy, Pcmap, colors, map_entries);
	}
	for(i = 0; i < map_entries; i++)
	{
		closenesses[i].cols_index = i;
		closenesses[i].closeness = USED_DIST(
			(int)(c->red),
			(int)(c->green),
			(int)(c->blue),
			(int)(colors[i].red),
			(int)(colors[i].green),
			(int)(colors[i].blue));
	}
	qsort(closenesses, map_entries, sizeof(CloseColor), closeness_cmp);

	i = 0;
	j = closenesses[i].cols_index;
	while (force ||
	       (abs((long)c->red - (long)colors[j].red) <=
		PICTURE_COLOR_CLOSENESS &&
		abs((long)c->green - (long)colors[j].green) <=
		PICTURE_COLOR_CLOSENESS &&
		abs((long)c->blue - (long)colors[j].blue) <=
		PICTURE_COLOR_CLOSENESS))
	{
			tmp.red = colors[j].red;
			tmp.green = colors[j].green;
			tmp.blue = colors[j].blue;
			if (XAllocColor(Pdpy, Pcmap, &tmp))
			{
				c->red = tmp.red;
				c->green = tmp.green;
				c->blue = tmp.blue;
				c->pixel = tmp.pixel;
				return 1;
			}
			else
			{
				i++;
				if (i == map_entries)
					break;
				j = closenesses[i].cols_index;
			}
	}
	return 0;
}

/*
 * dithering
 */

static
int my_dither(int x, int y, XColor *c)
{
        /* the dither matrice */
	static const char DM[128][128] = DITHER_MATRICE;
	int index;
	const char *dmp;

	if (Pcsi.grey_bits != 0)
	{
		/* Grey Scale */
		int prec = Pcsi.grey_bits;

		if (Pcsi.grey_bits == 1)
		{
			/* FIXME, can we do a better dithering */
			prec = 2;
		}
		dmp = DM[(0 + y) & (DM_HEIGHT - 1)];
		index = (c->green + ((c->blue + c->red) >> 1)) >> 1;
		index += (dmp[(0 + x) & (DM_WIDTH - 1)] << 2) >> prec;
		index = (index - (index >> prec));
		index = index >> (8 - Pcsi.grey_bits);
	}
	else
	{
		/* color cube */
		int dith, rs, gs, bs, gb, b;
		int tr,tb,tg;

		rs = Pcsi.d_nr - 1;
		gs = Pcsi.d_ng - 1;
		bs = Pcsi.d_nb - 1;
		gb = Pcsi.d_ng*Pcsi.d_nb;
		b = Pcsi.d_nb;

		dmp = DM[(0 + y) & (DM_HEIGHT - 1)];
		dith = (dmp[(0 + x) & (DM_WIDTH - 1)] << 2) | 7;
		tr = ((c->red * rs) + dith) >> 8;
		tg = ((c->green * gs) + (262 - dith)) >> 8;
		tb = ((c->blue * bs) + dith) >> 8;
		index = tr * gb + tg * b + tb;
#if 0
		/* try to use the additonal grey. Not easy, good for
		 * certain image/gradient bad for others */
		if (Pcsi.d_ngrey_bits)
		{
			int g_index;

			/* dither in the Pcsi.ngrey^3 cc */
			tr = ((c->red * (Pcsi.ngrey-1)) + dith) >> 8;
			tg = ((c->green * (Pcsi.ngrey-1)) + (262 - dith)) >> 8;
			tb = ((c->blue * (Pcsi.ngrey-1)) + dith) >> 8;
			/* get the grey */
			fprintf(stderr, "%i,%i,%i(%i/%i) ", tr,tg,tb,
				abs(tr-tg) + abs(tb-tg) + abs(tb-tr),Pcsi.ngrey);
			g_index = ((tr + tg + tb)/3);
			if (g_index != 0 && g_index != Pcsi.ngrey-1 &&
			    abs(tr-tg) + abs(tb-tg) + abs(tb-tr) <=
			    Pcsi.d_ngrey_bits)
			{
				g_index =  g_index + Pcsi.ng*Pcsi.nb*Pcsi.ng -1;
				index = g_index;
			}
		}
#endif
		if (PDitherMappingTable != NULL)
		{
			index = PDitherMappingTable[index];
		}
	}
	return index;
}

static
int my_dither_depth_15_16_init(void)
{
	const unsigned char _dither_44[4][4] =
	{
		{0, 4, 1, 5},
		{6, 2, 7, 3},
		{1, 5, 0, 4},
		{7, 3, 6, 2}
	};
	int y,x,i;
	int rm = 0xf8, re = 0x7, gm = 0xfc, ge = 0x3, bm = 0xf8, be = 0x7;

	if (Pdepth == 16 && (Pvisual->red_mask == 0xf800) &&
	    (Pvisual->green_mask == 0x7e0) &&
	    (Pvisual->blue_mask == 0x1f))
	{
		/* ok */
	}
	else if (Pdepth == 15 && (Pvisual->red_mask == 0x7c00) &&
		 (Pvisual->green_mask == 0x3e0) &&
		 (Pvisual->blue_mask == 0x1f))
	{
		gm = 0xf8; ge = 0x7;
	}
	else
	{
		return 0; /* fail */
	}

	Pcsi.red_dither =
		(unsigned short *)safemalloc(4*4*256*sizeof(unsigned short));
	Pcsi.green_dither =
		(unsigned short *)safemalloc(4*4*256*sizeof(unsigned short));
	Pcsi.blue_dither =
		(unsigned short *)safemalloc(4*4*256*sizeof(unsigned short));

	for (y = 0; y < 4; y++)
	{
		for (x = 0; x < 4; x++)
		{
			for (i = 0; i < 256; i++)
			{
				if ((_dither_44[x][y] < (i & re)) &&
				    (i < (256 - 8)))
				{
					Pcsi.red_dither[
						(x << 10) | (y << 8) | i] =
						((i + 8) & rm) << 8;
				}
				else
				{
					Pcsi.red_dither[
						(x << 10) | (y << 8) | i] =
						(i & rm) << 8;
				}
				if ((_dither_44[x][y] < ((i & ge) << 1))
				    && (i < (256 - 4)))
				{
					Pcsi.green_dither[
						(x << 10) | (y << 8) | i] =
						((i + 4) & gm) << 8;
				}
				else
				{
					Pcsi.green_dither[
						(x << 10) | (y << 8) | i] =
						(i & gm) << 8;
				}
				if ((_dither_44[x][y] < (i & be)) &&
				    (i < (256 - 8)))
				{
					Pcsi.blue_dither[
						(x << 10) | (y << 8) | i] =
						((i + 8) & bm) << 8;
				}
				else
				{
					Pcsi.blue_dither[
						(x << 10) | (y << 8) | i] =
						(i & bm) << 8;
				}
			}
		}
	}
	return 1;
}

/*
 * Color allocation in the "palette"
 */

static
int alloc_color_in_pct(XColor *c, int index)
{
	if (Pct[index].alloc_count == 0)
	{
		int s = PStrictColorLimit;

		PStrictColorLimit = 0;
		c->red = Pct[index].color.red;
		c->green = Pct[index].color.green;
		c->blue = Pct[index].color.blue;
		PictureAllocColor(Pdpy, Pcmap, c, True); /* WARN (rec) */
		Pct[index].color.pixel = c->pixel;
		Pct[index].alloc_count = 1;
		PStrictColorLimit = s;
	}
	else
	{
		c->red = Pct[index].color.red;
		c->green = Pct[index].color.green;
		c->blue = Pct[index].color.blue;
		c->pixel = Pct[index].color.pixel;
		if (Pct[index].alloc_count < 0xffffffff)
			(Pct[index].alloc_count)++;
	}
	return 1;
}

static
int get_color_index(int r, int g, int b, int is_8)
{
	int index;

	if (!is_8)
	{
		r= r >> 8;
		g= g >> 8;
		b= b >> 8;
	}
	if (Pcsi.grey_bits > 0)
	{
		/* FIXME: Use other proporition ? */
		index = ((r+g+b)/3) >> (8 - Pcsi.grey_bits);
	}
	else
	{
#if 1
		/* "exact" computation (corrected linear dist) */
		float fr,fg,fb;
		int ir, ig, ib;

		/* map to the cube */
		fr = ((float)r * (Pcsi.nr-1))/255;
		fg = ((float)g * (Pcsi.ng-1))/255;
		fb = ((float)b * (Pcsi.nb-1))/255;

		if (PMappingTable != NULL)
		{
			ir = (int)fr + (fr - (int)fr > 0.5);
			ig = (int)fg + (fg - (int)fg > 0.5);
			ib = (int)fb + (fb - (int)fb > 0.5);

			index = ir * Pcsi.ng*Pcsi.nb + ig * Pcsi.nb + ib;
		}
		else
		{
			/* found the best of the 8 linear closest points */
			int lr,lg,lb,tr,tg,tb,best_dist = -1,i,d;

			index = 0;
			lr = min((int)fr+1,Pcsi.nr-1);
			lg = min((int)fg+1,Pcsi.ng-1);
			lb = min((int)fb+1,Pcsi.nb-1);
			for(tr =(int)fr; tr<=lr; tr++)
			{
				for(tg =(int)fg; tg<=lg; tg++)
				{
					for(tb =(int)fb; tb<=lb; tb++)
					{
						i = tr * Pcsi.ng*Pcsi.nb +
							tg * Pcsi.nb +
							tb;
						d = USED_DIST(
							r,g,b,
							(Pct[i].color.red>>8),
							(Pct[i].color.green>>8),
							(Pct[i].color.blue>>8));
						if (best_dist == -1 ||
						    d < best_dist)
						{
							index = i;
							best_dist = d;
						}
					}
				}
			}

			/* now found the best grey */
			if (Pcsi.ngrey - 2 > 0)
			{
				/* FIXME: speedup this with more than 8 grey */
				int start = Pcsi.nr*Pcsi.ng*Pcsi.nb;
				for(i=start; i < start+Pcsi.ngrey-2; i++)
				{
					d = USED_DIST(
						r,g,b,
						(Pct[i].color.red>>8),
						(Pct[i].color.green>>8),
						(Pct[i].color.blue>>8));
					if (d < best_dist)
					{
						index = i;
						best_dist = d;
					}
				}
			}
			return index;
		}
#else
		/* approximation; faster */
		index = ((r * Pcsi.nr)>>8) * Pcsi.ng*Pcsi.nb +
			((g * Pcsi.ng)>>8) * Pcsi.nb +
			((b * Pcsi.nb)>>8);
#endif
		if (PMappingTable != NULL)
		{
			index = PMappingTable[index];
		}
	}
	return index;
}

/*
 * Main colors allocator
 */
static
int alloc_color_proportion(Display *dpy, Colormap cmap, XColor *c)
{
	c->pixel = (Pixel)(
		((c->red   >> (16 - Pcsi.red_prec))<< Pcsi.red_shift) +
		((c->green >> (16 - Pcsi.green_prec))<< Pcsi.green_shift) +
		((c->blue  >> (16 - Pcsi.blue_prec))<< Pcsi.blue_shift)
		);
	return 1;
}

static
int alloc_color_proportion_dither(
	Display *dpy, Colormap cmap, XColor *c, int x, int y)
{
	/* 8 bit colors !! */
	c->red = Pcsi.red_dither[
		(((x + 0) & 0x3) << 10) | ((y & 0x3) << 8) |
		((c->red) & 0xff)] * 257;
	c->green = Pcsi.green_dither[
		(((x + 0) & 0x3) << 10) | ((y & 0x3) << 8) |
		((c->green) & 0xff)] * 257;
	c->blue = Pcsi.blue_dither[
		(((x + 0) & 0x3) << 10) | ((y & 0x3) << 8) |
		((c->blue) & 0xff)] * 257;
	c->pixel = (Pixel)(
		((c->red >> (16 - Pcsi.red_prec)) << Pcsi.red_shift) +
		((c->green >> (16 - Pcsi.green_prec))
		 << Pcsi.green_shift) +
		((c->blue >> (16 - Pcsi.blue_prec)) << Pcsi.blue_shift)
		);
	return 1;
}

static
int alloc_color_proportion_grey(
	Display *dpy, Colormap cmap, XColor *c)
{
	/* FIXME: is this ok in general? */
	c->pixel = ((c->red + c->green + c->blue)/3);
	if (Pdepth < 16)
	{
		c->pixel = c->pixel >> (16 - Pdepth);
	}
	return 1;
}

static
int alloc_color_in_table(Display *dpy, Colormap cmap, XColor *c)
{

	int index = get_color_index(c->red,c->green,c->blue, False);
	return alloc_color_in_pct(c, index);
}

static
int alloc_color_in_table_dither(
	Display *dpy, Colormap cmap, XColor *c, int x, int y)
{
	int index;
	/* 8 bit colors !! */
	index = my_dither(x, y, c);
	return alloc_color_in_pct(c, index);
}

static
int alloc_color_dynamic_no_limit(
	Display *dpy, Colormap cmap, XColor *c)
{
	int r = 0;

	if (XAllocColor(dpy, cmap, c))
	{
		r = 1;
	}
	else if (!alloc_color_in_cmap(c, False))
	{
		XGrabServer(dpy);
		r = alloc_color_in_cmap(c, True);
		XUngrabServer(dpy);
	}
	else
	{
		r = 1;
	}
	if (r && Pac != NULL && (c->pixel <= (1 << Pdepth) /* always true*/))
	{
		Pac[c->pixel].alloc_count++;
		Pac[c->pixel].color.red = c->red;
		Pac[c->pixel].color.green = c->green;
		Pac[c->pixel].color.blue = c->blue;
		Pac[c->pixel].color.pixel = c->pixel;
	}
	return r;
}

static
int alloc_color_x(
	Display *dpy, Colormap cmap, XColor *c)
{
	return XAllocColor(dpy, cmap, c);
}

static
void free_colors_in_table(
	Display *dpy, Colormap cmap, Pixel *pixels, int n,
	unsigned long planes)
{
	Pixel *p;
	int i,j,do_free;
	int m = 0;

	if (!Pct || !PUseDynamicColors)
	{
		return;
	}

	p = (Pixel *)safemalloc(n*sizeof(Pixel));
	for(i= 0; i < n; i++)
	{
		do_free = 1;
		for(j=0; j<PColorLimit; j++)
		{
			if (Pct[j].alloc_count &&
			    Pct[j].alloc_count < 0xffffffff &&
			    pixels[i] == Pct[j].color.pixel)
			{
				(Pct[j].alloc_count)--;
				if (Pct[j].alloc_count)
					do_free = 0;
				break;
			}
		}
		if (do_free)
		{
			p[m++] = pixels[i];
		}
	}
	if (m > 0)
	{
		XFreeColors(dpy, cmap, p, m, planes);
	}
	free(p);

	return;
}

static
void free_colors_x(
	Display *dpy, Colormap cmap, Pixel *pixels, int n,
	unsigned long planes)
{
	XFreeColors(dpy, cmap, pixels, n, planes);
	if (Pac != NULL)
	{
		int nbr_colors = (1 << Pdepth);
		int i;

		for(i= 0; i < n; i++)
		{
			if (pixels[i] <= nbr_colors)
			{
				Pac[pixels[i]].alloc_count--;
			}
		}
	}
}

/*
 * local function for building pallet (dynamic colors, private DirectColor
 * cmap)
 */
static
XColor *build_mapping_colors(int nr, int ng, int nb)
{
	int r, g, b, i;
	XColor *colors;

	colors = (XColor *)safemalloc(nr*ng*nb * sizeof(XColor));
	i = 0;
	for (r = 0; r < nr; r++)
	{
		for (g = 0; g < ng; g++)
		{
			for (b = 0; b < nb; b++)
			{
				colors[i].red =
					r * 65535 / (nr - 1);
				colors[i].green =
					g * 65535 / (ng - 1);
				colors[i].blue =
					b * 65535 / (nb - 1);
				i++;
			}
		}
	}
	return colors;
}

static short *build_mapping_table(int nr, int ng, int nb, Bool use_named)
{
	int size = nr*ng*nb;
	XColor *colors_map;
	short *Table;
	int i,j, minind;
	double mindst = 40000;
	double dst;

	colors_map = build_mapping_colors(nr, ng, nb);
	Table = (short *)safemalloc((size+1) * sizeof(short));
	for(i=0; i<size; i++)
	{
		minind = 0;
		for(j=0; j<PColorLimit; j++)
		{
			if (use_named)
			{
				/* for back ward compatibility */
				dst = TRUE_DIST(colors_map[i].red,
						colors_map[i].green,
						colors_map[i].blue,
						Pct[j].color.red,
						Pct[j].color.green,
						Pct[j].color.blue);
			}
			else
			{
				dst = USED_DIST(colors_map[i].red,
						colors_map[i].green,
						colors_map[i].blue,
						Pct[j].color.red,
						Pct[j].color.green,
						Pct[j].color.blue);
			}
			if (j == 0 || dst < mindst)
			{
				mindst=dst;
				minind=j;
			}
		}
		Table[i] = minind;
	}
	Table[size] = Table[size-1];
	free(colors_map);
	return Table;
}

static
void free_table_colors(PColor *color_table, int npixels)
{
	Pixel pixels[256];
	int i,n=0;

	if (npixels > 0)
	{
		for(i = 0; i < npixels; i++)
		{
			if (color_table[i].alloc_count)
			{
				pixels[n++] = color_table[i].color.pixel;
			}
			color_table[i].alloc_count = 0;
		}
		if (n > 0)
		{
			XFreeColors(Pdpy, Pcmap, pixels, n, 0);
		}
	}
}

/* FIXME: the DirectColor case */
static
int get_nbr_of_free_colors(int max_check)
{
	int check = 1;
	Pixel Pixels[256];
	int map_entries = (Pvisual->class == DirectColor)?
		(1 << Pdepth):Pvisual->map_entries;
	if (max_check < 1)
		return 0;
	if (map_entries > 256)
	{
		max_check = 256;
	}
	max_check = (max_check > map_entries) ? map_entries:max_check;
	while(1)
	{
		if (XAllocColorCells(
			Pdpy, Pcmap, False, NULL, 0, Pixels, check))
		{
			XFreeColors(Pdpy, Pcmap, Pixels, check, 0);
			check++;
		}
		else
		{
			return check-1;
		}
		if (check > max_check)
		{
			return check-1;
		}
	}
	return check-1;
}

static
PColor *alloc_color_cube(
	int nr, int ng, int nb, int ngrey, int grey_bits, Bool do_allocate)
{
	int r, g, b, grey, i, start_grey, end_grey;
	PColor *color_table;
	XColor color;
	int size;

	size = nr*ng*nb + ngrey + (1 << grey_bits)*(grey_bits != 0);
	if (grey_bits)
	{
		ngrey = (1 << grey_bits);
	}
	if (nr > 0 && ngrey > 0)
	{
		start_grey = 1;
		end_grey = ngrey - 1;
		size = size - 2;
	}
	else
	{
		start_grey = 0;
		end_grey = ngrey;
	}

	color_table = (PColor *)safemalloc((size+1) * sizeof(PColor));

	i = 0;

#if USE_GAMMA_CORECTION
#define CG(x) 65535.0 * pow((x)/65535.0,1/COLOR_GAMMA)
#define GG(x) 65535.0 * pow((x)/65535.0,1/GREY_GAMMA)
#else
#define CG(x) x
#define GG(x) x
#endif

	if (nr > 0)
	{
		for (r = 0; r < nr; r++)
		{
			for (g = 0; g < ng; g++)
			{
				for (b = 0; b < nb; b++)
				{
					color.red = CG(r * 65535 / (nr - 1));
					color.green = CG(g * 65535 / (ng - 1));
					color.blue = CG(b * 65535 / (nb - 1));
					if (do_allocate)
					{
						if (!XAllocColor(Pdpy, Pcmap,
								 &color))
						{
							free_table_colors(
								color_table, i);
							free(color_table);
							return NULL;
						}
						color_table[i].color.pixel =
							color.pixel;
						color_table[i].alloc_count = 1;
					}
					else
					{
						color_table[i].alloc_count = 0;
					}
					color_table[i].color.red = color.red;
					color_table[i].color.green = color.green;
					color_table[i].color.blue = color.blue;
					i++;
				}
			}
		}
	}

	if (ngrey > 0)
	{
		for (grey = start_grey; grey < end_grey; grey++)
		{
			color.red = color.green = color.blue =
				GG(grey * 65535 / (ngrey - 1));
			if (do_allocate)
			{
				if (!XAllocColor(Pdpy, Pcmap, &color))
				{
					free_table_colors(color_table, i);
					free(color_table);
					return NULL;
				}
				color_table[i].color.pixel = color.pixel;
				color_table[i].alloc_count = 1;
			}
			else
			{
				color_table[i].alloc_count = 0;
			}
			color_table[i].color.red = color.red;
			color_table[i].color.green = color.green;
			color_table[i].color.blue = color.blue;
			i++;
		}
	}
	color_table[size].color.red = color_table[size-1].color.red;
	color_table[size].color.green = color_table[size-1].color.green;
	color_table[size].color.blue = color_table[size-1].color.blue;
	color_table[size].color.pixel = color_table[size-1].color.pixel;
	color_table[size].alloc_count = 0;
	PColorLimit = size;
	return color_table;
}


static
PColor *alloc_named_ct(int *limit, Bool do_allocate)
{

/* First thing in base array are colors probably already in the color map
   because they have familiar names.
   I pasted them into a xpm and spread them out so that similar colors are
   spread out.
   Toward the end are some colors to fill in the gaps.
   Currently 61 colors in this list.
*/
	char *color_names[] =
	{
		"black",
		"white",
		"grey",
		"green",
		"blue",
		"red",
		"cyan",
		"yellow",
		"magenta",
		"DodgerBlue",
		"SteelBlue",
		"chartreuse",
		"wheat",
		"turquoise",
		"CadetBlue",
		"gray87",
		"CornflowerBlue",
		"YellowGreen",
		"NavyBlue",
		"MediumBlue",
		"plum",
		"aquamarine",
		"orchid",
		"ForestGreen",
		"lightyellow",
		"brown",
		"orange",
		"red3",
		"HotPink",
		"LightBlue",
		"gray47",
		"pink",
		"red4",
		"violet",
		"purple",
		"gray63",
		"gray94",
		"plum1",
		"PeachPuff",
		"maroon",
		"lavender",
		"salmon",           /* for peachpuff, orange gap */
		"blue4",            /* for navyblue/mediumblue gap */
		"PaleGreen4",       /* for forestgreen, yellowgreen gap */
		"#AA7700",          /* brick, no close named color */
		"#11EE88",          /* light green, no close named color */
		"#884466",          /* dark brown, no close named color */
		"#CC8888",          /* light brick, no close named color */
		"#EECC44",          /* gold, no close named color */
		"#AAAA44",          /* dull green, no close named color */
		"#FF1188",          /* pinkish red */
		"#992299",          /* purple */
		"#CCFFAA",          /* light green */
		"#664400",          /* dark brown*/
		"#AADD99",          /* light green */
		"#66CCFF",          /* light blue */
		"#CC2299",          /* dark red */
		"#FF11CC",          /* bright pink */
		"#11CC99",          /* grey/green */
		"#AA77AA",          /* purple/red */
		"#EEBB77"           /* orange/yellow */
	};
	int NColors = sizeof(color_names)/sizeof(char *);
	int i,rc;
	PColor *color_table;
	XColor color;

	*limit = (*limit > NColors)? NColors: *limit;
	color_table = (PColor *)safemalloc((*limit+1) * sizeof(PColor));
	for(i=0; i<*limit; i++)
	{
		rc=XParseColor(Pdpy, Pcmap, color_names[i], &color);
		if (rc==0) {
			fprintf(stderr,"color_to_rgb: can't parse color %s,"
				" rc %d\n", color_names[i], rc);
			free_table_colors(color_table, i);
			free(color_table);
			return NULL;
		}
		if (do_allocate)
		{
			if (!XAllocColor(Pdpy, Pcmap, &color))
			{
				free_table_colors(color_table, i);
				free(color_table);
				return NULL;
			}
			color_table[i].color.pixel = color.pixel;
			color_table[i].alloc_count = 1;
		}
		else
		{
			color_table[i].alloc_count = 0;
		}
		color_table[i].color.red = color.red;
		color_table[i].color.green = color.green;
		color_table[i].color.blue = color.blue;
	}
	color_table[*limit].color.red = color_table[*limit-1].color.red;
	color_table[*limit].color.green = color_table[*limit-1].color.green;
	color_table[*limit].color.blue = color_table[*limit-1].color.blue;
	color_table[*limit].color.pixel = color_table[*limit-1].color.pixel;
	color_table[*limit].alloc_count = 0;
	PColorLimit = *limit;
	return color_table;
}

static
void create_mapping_table(
	int nr, int ng, int nb, int ngrey, int grey_bits,
	Bool non_regular_pallet)
{

	Pcsi.grey_bits = 0;

	/* initialize dithering colors numbers */
	if (!non_regular_pallet)
	{
		/* */
		Pcsi.d_nr = nr;
		Pcsi.d_ng = ng;
		Pcsi.d_nb = nb;
		Pcsi.d_ngrey_bits = 2;
		while((1<<Pcsi.d_ngrey_bits) < ngrey)
		{
			Pcsi.d_ngrey_bits++;
		}
		if (1<<Pcsi.d_ngrey_bits != ngrey)
		{
			Pcsi.d_ngrey_bits = 0;
		}
		Pcsi.grey_bits = grey_bits;
	}
	else
	{
		/* dither table should be small */
		Pcsi.grey_bits = 0;
		if (PColorLimit <= 9)
		{
			Pcsi.d_nr = 3;
			Pcsi.d_ng = 3;
			Pcsi.d_nb = 3;
			Pcsi.d_ngrey_bits = 0;
		}
		else if (PColorLimit <= 64)
		{
			Pcsi.d_nr = 4;
			Pcsi.d_ng = 4;
			Pcsi.d_nb = 4;
			Pcsi.d_ngrey_bits = 0;
		}
		else
		{
			Pcsi.d_nr = 8;
			Pcsi.d_ng = 8;
			Pcsi.d_nb = 8;
			Pcsi.d_ngrey_bits = 0;
		}
		PDitherMappingTable = build_mapping_table(
			Pcsi.d_nr, Pcsi.d_ng, Pcsi.d_nb, non_regular_pallet);
	}

	/* initialize colors number fo index computation */
	if (PColorLimit == 2)
	{
		/* ok */
		Pcsi.nr = 0;
		Pcsi.ng = 0;
		Pcsi.nb = 0;
		Pcsi.ngrey = 0;
		Pcsi.grey_bits = 1;
	}
	else if (grey_bits > 0)
	{
		Pcsi.nr = 0;
		Pcsi.ng = 0;
		Pcsi.nb = 0;
		Pcsi.ngrey = 0;
		Pcsi.grey_bits = grey_bits;
	}
	else if (non_regular_pallet || (0&&ngrey>0))
	{
		/* note: using these table with !used_named && ngrey>0 will
		 * probably leads to faster image loading. But I see nothing
		 * of significative. On the others hands not using it gives
		 * maybe better colors approximation. */
		if (PColorLimit <= 9)
		{
			Pcsi.nr = 8;
			Pcsi.ng = 8;
			Pcsi.nb = 8;
			Pcsi.ngrey = 0;
		}
		else
		{
			Pcsi.nr = 16;
			Pcsi.ng = 16;
			Pcsi.nb = 16;
			Pcsi.ngrey = 0;
		}
		PMappingTable = build_mapping_table(
			Pcsi.nr, Pcsi.ng, Pcsi.nb, non_regular_pallet);
	}
	else
	{
		Pcsi.nr = nr;
		Pcsi.ng = ng;
		Pcsi.nb = nb;
		Pcsi.ngrey = ngrey;
		Pcsi.grey_bits = 0;
	}
}

static void finish_ct_init(
	int call_type, int ctt, int nr, int ng, int nb, int ngrey,
	int grey_bits, Bool use_named)
{
	if (call_type == PICTURE_CALLED_BY_FVWM)
	{
		char *env;

		if (PAllocTable)
		{
			ctt = PICTURE_PAllocTable + ctt;
		}
		if (PUseDynamicColors)
		{
			ctt = PICTURE_PUseDynamicColors + ctt;
		}
		if (PStrictColorLimit)
		{
			ctt = PICTURE_PStrictColorLimit + ctt;
		}
		if (use_named)
		{
			ctt = PICTURE_use_named + ctt;
		}
		else
		{
			ctt++;
		}
		env = safemalloc(PICTURE_TABLETYPE_LENGHT + 1);
		sprintf(env, "%i", ctt);
		flib_putenv("FVWM_COLORTABLE_TYPE", env);
		free(env);
		if (Pdepth <= 8)
		{
			Pac = (PColor *)safecalloc(
				(1 << Pdepth), sizeof(PColor));
		}
	}

	if (Pct)
	{
		if (!PAllocTable && call_type == PICTURE_CALLED_BY_FVWM)
		{
			free_table_colors(Pct, PColorLimit);
		}
		create_mapping_table(nr,ng,nb,ngrey,grey_bits,use_named);
	}
}

#define PA_COLOR_CUBE (1 << 1)
#define FVWM_COLOR_CUBE (1 << 2)
#define PA_GRAY_SCALE (1 << 3)
#define FVWM_GRAY_SCALE (1 << 4)
#define ANY_COLOR_CUBE (PA_COLOR_CUBE|FVWM_COLOR_CUBE)
#define ANY_GRAY_SCALE (PA_GRAY_SCALE|FVWM_GRAY_SCALE)

static
int PictureAllocColorTable(
	PictureColorLimitOption *opt, int call_type, Bool use_my_color_limit)
{
	char *envp;
	int free_colors, nbr_of_color, limit, cc_nbr, i, size;
	int use_named_table = 0;
	int do_allocate = 0;
	int use_default = 1;
	int private_cmap = !(Pdefault);
	int dyn_cl_set = False;
	int strict_cl_set = False;
	int alloc_table_set = False;
	int color_limit;
	int pa_type = (Pvisual->class != GrayScale) ?
		PA_COLOR_CUBE : PA_GRAY_SCALE;
	int fvwm_type = (Pvisual->class != GrayScale) ?
		FVWM_COLOR_CUBE : FVWM_GRAY_SCALE;
	int cc[][6] =
	{
		/* {nr,ng,nb,ngrey,grey_bits,logic} */
		/* 5 first for direct colors and Pdepth > 8*/
		/* 8192 colors depth 13, a reasonable max for a color table */
		{16, 32, 16, 0, 0, FVWM_COLOR_CUBE},
		/* 4096 colors depth 12 */
		{16, 16, 16, 0, 0, FVWM_COLOR_CUBE},
		/* 1024 colors depth 10 */
		{8, 16, 8, 0, 0, FVWM_COLOR_CUBE},
		/* 512 colors depth 9 */
		{8, 8, 8, 0, 0, FVWM_COLOR_CUBE},
		/* 256 colors 3/3/2 standard colormap */
		{8, 8, 4, 0, 0, FVWM_COLOR_CUBE},
		/* 256 grey scale */
		{0, 0, 0, 0, 8, ANY_GRAY_SCALE},
		/* 244 Xrender XFree-4.2 */
		{6, 6, 6, 30, 0, ANY_COLOR_CUBE},
		/* 216 Xrender XFree-4.2,GTK/QT "default cc" */
		{6, 6, 6, 0, 0, ANY_COLOR_CUBE},
		/* 180 (GTK) */
		{6, 6, 5, 0, 0, ANY_COLOR_CUBE},
		/* 144 (GTK) */
		{6, 6, 4, 0, 0, ANY_COLOR_CUBE},
		/* 128 grey scale */
		{0, 0, 0, 0, 7, ANY_GRAY_SCALE},
		/* 125 GTK mini default cc (may change? 444) */
		{5, 5, 5, 0, 0, ANY_COLOR_CUBE},
		/* 100 (GTK with color limit) */
		{5, 5, 4, 0, 0, ANY_COLOR_CUBE},
		/* 85 Xrender XFree-4.3 */
		{4, 4, 4, 23, 0, ANY_COLOR_CUBE},
		/* 78 (in fact 76)  a good default ??*/
		{4, 4, 4, 16, 0, FVWM_COLOR_CUBE},
		/* 70  a good default ?? */
		{4, 4, 4, 8, 0, ANY_COLOR_CUBE},
		/* 68  a good default ?? */
		{4, 4, 4, 6, 0, ANY_COLOR_CUBE},
		/* 64 Xrender XFree-4.3 (GTK wcl) */
		{4, 4, 4, 0, 0, ANY_COLOR_CUBE},
		/* 64 grey scale */
		{0, 0, 0, 0, 6, ANY_GRAY_SCALE},
		/* 54, maybe a good default? */
		{4, 4, 3, 8, 0, FVWM_COLOR_CUBE},
		/* 48, (GTK wcl) no grey but ok */
		{4, 4, 3, 0, 0, FVWM_COLOR_CUBE},
		/* 32, 2/2/1 standard colormap */
		{4, 4, 2, 0, 0, FVWM_COLOR_CUBE},
		/* 32 xrender xfree-4.2 */
		{0, 0, 0, 0, 6, ANY_GRAY_SCALE},
		/* 29 */
		{3, 3, 3, 4, 0, FVWM_COLOR_CUBE},
		/* 27 (xrender in depth 6&7(hypo) GTK wcl) */
		{3, 3, 3, 0, 0, FVWM_COLOR_CUBE|PA_COLOR_CUBE*(Pdepth<8)},
		/* 16 grey scale */
		{0, 0, 0, 0, 4, FVWM_GRAY_SCALE},
		/* 10 */
		{2, 2, 2, 4, 0, FVWM_COLOR_CUBE},
                /* 8 (xrender/qt/gtk wcl) */
		{2, 2, 2, 0, 0, FVWM_COLOR_CUBE},
		/* 8 grey scale Xrender depth 4 and XFree-4.3 */
		{0, 0, 0, 0, 3, FVWM_GRAY_SCALE|PA_GRAY_SCALE*(Pdepth<5)},
		/* 4 grey scale*/
		{0, 0, 0, 0, 2,
		 FVWM_GRAY_SCALE|FVWM_COLOR_CUBE|PA_COLOR_CUBE*(Pdepth<4)},
		/* 2 */
		{0, 0, 0, 0, 1, FVWM_COLOR_CUBE|FVWM_GRAY_SCALE}
	};

	cc_nbr = sizeof(cc)/(sizeof(cc[0]));

	/* set up  default */
	PStrictColorLimit = 0;
	PUseDynamicColors = 1;
	PAllocTable = 0;
	use_named_table = False;
	color_limit = 0;
	use_default = True;

	/* use fvwm color limit */
	if (!use_my_color_limit &&
	     (envp = getenv("FVWM_COLORTABLE_TYPE")) != NULL)
	{
		int nr = 0, ng = 0, nb = 0, grey_bits = 0, ngrey = 0;
		int ctt = atoi(envp);

		if (ctt >= PICTURE_PAllocTable)
		{
			ctt -= PICTURE_PAllocTable;
			PAllocTable = 1; /* not useful for a module !*/
		}
		if (ctt >= PICTURE_PUseDynamicColors)
		{
			PUseDynamicColors = 1;
			ctt -= PICTURE_PUseDynamicColors;
		}
		if (ctt >= PICTURE_PStrictColorLimit)
		{
			PStrictColorLimit = 1;
			ctt -= PICTURE_PStrictColorLimit;
		}
		if (ctt >= PICTURE_use_named)
		{
			ctt -= PICTURE_use_named;
			Pct = alloc_named_ct(&ctt, False);
			use_named_table = True;
		}
		else if (ctt == 0)
		{
			/* depth <= 8 and no colors limit ! */
			PColorLimit = 0;
			return 0;
		}
		else if (ctt <= cc_nbr)
		{
			ctt--;
			Pct = alloc_color_cube(
				cc[ctt][0], cc[ctt][1], cc[ctt][2], cc[ctt][3],
				cc[ctt][4],
				False);
			nr = cc[ctt][0];
			ng = cc[ctt][1];
			nb = cc[ctt][2];
			ngrey = cc[ctt][3];
			grey_bits = cc[ctt][4];
		}
		if (Pct != NULL)
		{
			/* should always happen */
			finish_ct_init(
				call_type, ctt, nr, ng, nb, ngrey, grey_bits,
				use_named_table);
			return PColorLimit;
		}
	}

	nbr_of_color = (1 << Pdepth);
	color_limit = 0;

	/* parse the color limit env variable */
	if ((envp = getenv("FVWM_COLORLIMIT")) != NULL)
	{
		char *rest, *l;

		rest = GetQuotedString(envp, &l, ":", NULL, NULL, NULL);
		if (l && *l != '\0' && (color_limit = atoi(l)) >= 0)
		{
			use_default = 0;
		}
		if (l != NULL)
		{
			free(l);
		}
		if (color_limit == 9 || color_limit == 61)
		{
			use_named_table = 1;
		}
		if (rest && *rest != '\0')
		{
			if (rest[0] == '1')
			{
				strict_cl_set = True;
				PStrictColorLimit = 1;
			}
			else
			{
				strict_cl_set = True;
				PStrictColorLimit = 0;
			}
			if (strlen(rest) > 1 && rest[1] == '1')
			{
				use_named_table = 1;
			}
			else
			{
				use_named_table = 0;
			}
			if (strlen(rest) > 2 && rest[2] == '1')
			{
				dyn_cl_set = True;
				PUseDynamicColors = 1;
			}
			else
			{
				dyn_cl_set = True;
				PUseDynamicColors = 0;
			}
			if (strlen(rest) > 3 && rest[3] == '1')
			{
				alloc_table_set = True;
				PAllocTable = 1;
			}
			else
			{
				alloc_table_set = True;
				PAllocTable = 0;
			}
		}
	}
	else if (opt != NULL) /* use the option */
	{
		if (opt->color_limit > 0)
		{
			use_default = 0;
			color_limit = opt->color_limit;
		}
		if (color_limit == 9 || color_limit == 61)
		{
			use_named_table = 1;
		}
		if (opt->strict > 0)
		{
			strict_cl_set = True;
			PStrictColorLimit = 1;
		}
		else if (opt->strict == 0)
		{
			strict_cl_set = True;
			PStrictColorLimit = 0;
		}
		if (opt->use_named_table > 0)
		{
			use_named_table = 1;
		}
		else if (opt->use_named_table == 0)
		{
			use_named_table = 0;
		}
		if (opt->not_dynamic > 0)
		{
			dyn_cl_set = True;
			PUseDynamicColors = 0;
		}
		else if (opt->not_dynamic == 0)
		{
			dyn_cl_set = True;
			PUseDynamicColors = 0;
		}
		if (opt->allocate > 0)
		{
			alloc_table_set = True;
			PAllocTable = 1;
		}
		else if (opt->allocate == 0)
		{
			alloc_table_set = True;
			PAllocTable = 0;
		}
	}

	if (color_limit <= 0)
	{
		use_default = 1;
		color_limit = nbr_of_color;
	}

	/* first try to see if we have a "pre-allocated" color cube.
	 * The bultin RENDER X extension pre-allocate a color cube plus
	 * some grey's (xc/programs/Xserver/render/miindex)
	 * See gdk/gdkrgb.c for the cubes used by gtk+-2, 666 is the default,
	 * 555 is the minimal cc (this may change): if gtk cannot allocate
	 * the 555 cc (or better) a private cmap is used.
	 * for qt-3: see src/kernel/{qapplication.cpp,qimage.cpp,qcolor_x11.c}
	 * the 666 cube is used by default (with approx in the cmap if some
	 * color allocation fail), and some qt app may accept an
	 * --ncols option to limit the nbr of colors, then some "2:3:1"
	 * proportions color cube are used (222, 232, ..., 252, 342, ..., 362,
	 * 452, ...,693, ...)
	 * imlib2 try to allocate the 666 cube if this fail it try more
	 * exotic table (see rend.c and rgba.c) */
	i = 0;
	free_colors = 0;
	if (Pdepth <= 8 && !private_cmap && use_default &&
	    i < cc_nbr && Pct == NULL && (Pvisual->class & 1))
	{
		free_colors = get_nbr_of_free_colors(nbr_of_color);
	}
	while(Pdepth <= 8 && !private_cmap && use_default &&
	      i < cc_nbr && Pct == NULL && (Pvisual->class & 1))
	{
		size = cc[i][0]*cc[i][1]*cc[i][2] + cc[i][3] -
			2*(cc[i][3] > 0) + (1 << cc[i][4])*(cc[i][4] != 0);
		if (size > nbr_of_color || !(cc[i][5] & pa_type))
		{
			i++;
			continue;
		}
		if (free_colors <= nbr_of_color - size)
		{
			Pct = alloc_color_cube(
				cc[i][0], cc[i][1], cc[i][2], cc[i][3],
				cc[i][4], True);
		}
		if (Pct != NULL)
		{
			if (free_colors <=
			    get_nbr_of_free_colors(nbr_of_color))
			{
				/* done */
			}
			else
			{
				free_table_colors(Pct, PColorLimit);
				free(Pct);
				Pct = NULL;
			}
		}
		i++;
	}
	if (Pct != NULL)
	{
		PUseDynamicColors = 0;
		PAllocTable = 1;
		Pcsi.pre_allocated_pallet = 1;
		i = i - 1;
		finish_ct_init(
			call_type, i, cc[i][0], cc[i][1], cc[i][2], cc[i][3],
			cc[i][4], 0);
		return PColorLimit;
	}

	/*
	 * now use "our" table
	 */

	limit = (color_limit >= nbr_of_color)? nbr_of_color:color_limit;

	if (use_default && !private_cmap)
	{
		/* XRender cvs default: */
#if 0
		if (limit > 100)
			limit = nbr_of_color/3;
		else
			limit = nbr_of_color/2;
		/* depth 8: 85 */
		/* depth 4: 8 */
#endif
		if (limit > 256)
		{
			/* direct colors & Pdepth > 8 */
			if (Pdepth >= 16)
			{
				limit = 8192;
			}
			else if (Pdepth >= 15)
			{
				limit = 4096;
			}
			else
			{
				limit = 512;
			}
		}
		else if (limit == 256)
		{
			if (Pvisual->class == GrayScale)
			{
				limit = 64;
			}
			else if (Pvisual->class == DirectColor)
			{
				limit = 32;
			}
			else
			{
				limit = 68;
				/* candidate:
				 * limit = 54;  4x4x3 + 6 grey
				 * limit = 61 (named table)
				 * limit = 85 current XRender default 4cc + 21
				 * limit = 76 future(?) XRender default 4cc + 16
				 * limit = 68 4x4x4 + 4
				 * limit = 64 4x4x4 + 0 */
			}

		}
		else if (limit == 128 || limit == 64)
		{
			if (Pvisual->class == GrayScale)
			{
				limit = 32;
			}
			else
			{
				limit = 31;
			}
		}
		else if (limit >= 16)
		{
			if (Pvisual->class == GrayScale)
			{
				limit = 8;
			}
			else
			{
				limit = 10;
			}
		}
		else if (limit >= 8)
		{
			limit = 4;
		}
		else
		{
			limit = 2;
		}
	}
	if (limit < 2)
	{
		limit = 2;
	}

	if (Pvisual->class == DirectColor)
	{
		/* humm ... Any way this case should never happen in real life:
		 * DirectColor default colormap! */
		PUseDynamicColors = 0;
		PAllocTable = 1;
		PStrictColorLimit = 1;
	}
	if (PAllocTable)
	{
		do_allocate = 1;
	}
	else
	{
		do_allocate = 0;
	}

	/* use the named table ? */
	if (use_named_table)
	{
		i = limit;
		while(Pct == NULL && i >= 2)
		{
			Pct = alloc_named_ct(&i, do_allocate);
			i--;
		}
	}
	if (Pct != NULL)
	{
		finish_ct_init(
			call_type, PColorLimit, 0, 0, 0, 0, 0, 1);
		return PColorLimit;
	}

	/* color cube or regular grey scale */
	i = 0;
	while(i < cc_nbr && Pct == NULL)
	{
		if ((cc[i][5] & fvwm_type) &&
		    cc[i][0]*cc[i][1]*cc[i][2] + cc[i][3] - 2*(cc[i][3] > 0) +
		    (1 << cc[i][4])*(cc[i][4] != 0) <= limit)
		{
			Pct = alloc_color_cube(
				cc[i][0], cc[i][1], cc[i][2], cc[i][3], cc[i][4],
				do_allocate);
		}
		i++;
	}
	if (Pct != NULL)
	{
		i = i-1;
		finish_ct_init(
			call_type, i, cc[i][0], cc[i][1], cc[i][2], cc[i][3],
			cc[i][4], 0);
		return PColorLimit;
	}

	/* I do not think we can be here */
	Pct = alloc_color_cube(0, 0, 0, 0, 1, False);
	finish_ct_init(call_type, cc_nbr-1, 0, 0, 0, 0, 1, 0);
	if (Pct == NULL)
	{
		fprintf(stderr,
			"[fvwm] ERR -- Cannot get Black and White. exiting!\n");
		exit(2);
	}
	return PColorLimit;
}

/*
 * Allocation of a private DirectColor cmap this is broken for depth > 16
 */
static
Bool alloc_direct_colors(int *limit, Bool use_my_color_limit)
{
	unsigned long nr,ng,nb,r,g,b,cr,cg,cf,pr,pg;
	unsigned long red_mask, green_mask, blue_mask;
	XColor *colors;

	if (Pdepth <= 16)
	{
		red_mask = Pvisual->red_mask;
		green_mask = Pvisual->green_mask;
		blue_mask = Pvisual->blue_mask;
	}
	else
	{
		/* Use a standard depth 16 colormap. This is broken FIXME! */
		red_mask = 0xf800;
		green_mask = 0x7e0;
		blue_mask = 0x1f;
	}

	decompose_mask(
		red_mask, &Pcsi.red_shift, &Pcsi.red_prec);
	decompose_mask(
		green_mask, &Pcsi.green_shift, &Pcsi.green_prec);
	decompose_mask(
		blue_mask, &Pcsi.blue_shift, &Pcsi.blue_prec);

	if (!use_my_color_limit)
	{
		/* colors allocated by fvwm we can return */
		return 1;
	}

	nr = 1 << Pcsi.red_prec;
	ng = 1 << Pcsi.green_prec;
	nb = 1 << Pcsi.blue_prec;

	colors = (XColor *)safemalloc(nb*sizeof(XColor));
	cf = DoRed|DoBlue|DoGreen;
	for (r=0; r<nr; r++)
	{
		cr = r * 65535 / (nr - 1);
		pr = (cr >> (16 - Pcsi.red_prec)) << Pcsi.red_shift;
		for (g = 0; g < ng; g++)
		{
			cg = g * 65535 / (ng - 1);
			pg = (cg >> (16 - Pcsi.green_prec)) << Pcsi.green_shift;
			for (b = 0; b < nb; b++)
			{
				colors[b].flags = cf;
				colors[b].red = cr;
				colors[b].green = cg;
				colors[b].blue = b * 65535 / (nb - 1);
				colors[b].pixel =
					(Pixel)(pr + pg +
						((colors[b].blue  >>
						  (16 - Pcsi.blue_prec)) <<
						 Pcsi.blue_shift));
			}
			XStoreColors(Pdpy, Pcmap, colors, nb);
		}
	}
	free(colors);
	return 1;
}

/*
 * Init the table for Static Colors
 */
static
void init_static_colors_table(void)
{
	XColor colors[256];
	int i;
	int nbr_of_colors = min(256, (1 << Pdepth));

	PColorLimit = nbr_of_colors;
	Pct = (PColor *)safemalloc((nbr_of_colors+1) * sizeof(PColor));
	for (i = 0; i < nbr_of_colors; i++)
	{
		colors[i].pixel = Pct[i].color.pixel = i;
	}
	XQueryColors(Pdpy, Pcmap, colors, nbr_of_colors);
	for (i = 0; i < nbr_of_colors; i++)
	{
		Pct[i].color.red = colors[i].red;
		Pct[i].color.green = colors[i].green;
		Pct[i].color.blue = colors[i].blue;
		Pct[i].alloc_count = 1;
	}
	Pct[PColorLimit].color.red = Pct[PColorLimit-1].color.red;
	Pct[PColorLimit].color.green = Pct[PColorLimit-1].color.green;
	Pct[PColorLimit].color.blue = Pct[PColorLimit-1].color.blue;
	Pct[PColorLimit].alloc_count = 1;
	create_mapping_table(0, 0, 0, 0, 0, True);
}


/*
 * misc local functions
 */
static
void print_colormap(Colormap cmap)
{
	XColor colors[256];
	int i;
	int nbr_of_colors = max(256, (1 << Pdepth));
	for (i = 0; i < nbr_of_colors; i++)
	{
		colors[i].pixel = i;
	}
	XQueryColors(Pdpy, cmap, colors, nbr_of_colors);
	for (i = 0; i < nbr_of_colors; i++)
	{
		fprintf(stderr,"    rgb(%.3i): %.3i/%.3i/%.3i\n", i,
			colors[i].red >> 8,
			colors[i].green >> 8,
			colors[i].blue >> 8);
	}
}

/* ---------------------------- interface functions ------------------------ */

int PictureAllocColor(Display *dpy, Colormap cmap, XColor *c, int no_limit)
{
	if (PStrictColorLimit && Pct != NULL)
	{
		no_limit = 0;
	}

	if (no_limit)
	{
		return Pcsi.alloc_color_no_limit(dpy, cmap, c);
	}
	else
	{
		return Pcsi.alloc_color(dpy, cmap, c);
	}
	return 0;
}


int PictureAllocColorAllProp(
	Display *dpy, Colormap cmap, XColor *c, int x, int y,
	Bool no_limit, Bool is_8, Bool do_dither)
{

	if (!no_limit && do_dither && Pcsi.alloc_color_dither != NULL)
	{
		if (!is_8)
		{
			c->red = c->red >> 8;
			c->green = c->green >> 8;
			c->blue = c->blue >> 8;
		}
		return Pcsi.alloc_color_dither(dpy, cmap, c, x, y);
	}
	else
	{
		if (is_8)
		{
			c->red = c->red << 8;
			c->green = c->green << 8;
			c->blue = c->blue << 8;
		}
		return PictureAllocColor(dpy, cmap, c, False);
	}
	return 0;
}

int PictureAllocColorImage(
	Display *dpy, PictureImageColorAllocator *pica, XColor *c, int x, int y)
{
	int r;

	r = PictureAllocColorAllProp(
		dpy, pica->cmap, c, x, y,
		pica->no_limit, pica->is_8, pica->dither);
	if (r && pica->pixels_table != NULL && pica->pixels_table_size &&
	    c->pixel < pica->pixels_table_size)
	{
		pica->pixels_table[c->pixel]++;
	}
	return r;
}

PictureImageColorAllocator *PictureOpenImageColorAllocator(
	Display *dpy, Colormap cmap, int x, int y, Bool no_limit,
	Bool do_not_save_pixels, int dither, Bool is_8)
{
	PictureImageColorAllocator *pica;
	Bool do_save_pixels = False;

	pica = (PictureImageColorAllocator *)safemalloc(
		sizeof(PictureImageColorAllocator));
	if (Pdepth <= 8 && !do_not_save_pixels && (Pvisual->class & 1) &&
	    ((PUseDynamicColors && Pct) || no_limit))
	{
		int s =  1 << Pdepth;
		pica->pixels_table = (unsigned long *)safecalloc(
			s, sizeof(unsigned long));
		pica->pixels_table_size = s;
		do_save_pixels = True;
	}
	if (!do_save_pixels)
	{
		pica->pixels_table = NULL;
		pica->pixels_table_size = 0;
	}
	pica->is_8 = is_8;
	if (dither && Pdepth <= 16)
	{
		pica->dither = dither;
	}
	else
	{
		pica->dither = dither;
	}
	pica->no_limit = no_limit;
	pica->cmap = cmap;
	return pica;
}

void PictureCloseImageColorAllocator(
	Display *dpy, PictureImageColorAllocator *pica,
	int *nalloc_pixels, Pixel **alloc_pixels, Bool *no_limit)
{
	if (nalloc_pixels)
	{
		*nalloc_pixels = 0;
	}
	if (alloc_pixels != NULL)
	{
		*alloc_pixels = NULL;
	}
	if (no_limit != NULL)
	{
		*no_limit = 0;
	}
	if (pica->pixels_table)
	{
		int i,j;
		int k = 0, l = 0;
		unsigned int np = 0;
		int free_num = 0;
		Pixel *free_pixels = NULL;
		Pixel *save_pixels = NULL;

		for(i = 0; i < pica->pixels_table_size; i++)
		{
			if (pica->pixels_table[i])
			{
				free_num += (pica->pixels_table[i]-1);
				np++;
			}
		}
		if (free_num)
		{
			free_pixels = (Pixel *)safemalloc(
				free_num * sizeof(Pixel));
		}
		if (np && nalloc_pixels != NULL && alloc_pixels != NULL)
		{
			save_pixels = (Pixel *)safemalloc(np * sizeof(Pixel));
		}
		for(i = 0; i < pica->pixels_table_size; i++)
		{
			if (pica->pixels_table[i])
			{
				if (save_pixels)
				{
					save_pixels[k++] = i;
				}
				for(j=1; j < pica->pixels_table[i]; j++)
				{
					free_pixels[l++] = i;
				}
			}
		}
		if (free_num)
		{
			PictureFreeColors(
				dpy, pica->cmap, free_pixels, free_num, 0,
				pica->no_limit);
			free(free_pixels);
		}
		if (nalloc_pixels != NULL && alloc_pixels != NULL)
		{
			*nalloc_pixels = np;
			*alloc_pixels = save_pixels;
			if (no_limit != NULL)
			{
				*no_limit = pica->no_limit;
			}
		}
		else if (save_pixels)
		{
			free(save_pixels);
		}
		free(pica->pixels_table);
	}
	free(pica);
	return;
}

void PictureFreeColors(
	Display *dpy, Colormap cmap, Pixel *pixels, int n,
	unsigned long planes, Bool no_limit)
{
	if (no_limit)
	{
		if (Pcsi.free_colors_no_limit != NULL)
		{
			Pcsi.free_colors_no_limit(
				dpy, cmap, pixels, n, planes);
		}
	}
	else
	{
		if (Pcsi.free_colors != NULL)
		{
			Pcsi.free_colors(dpy, cmap, pixels, n, planes);
		}
	}
	return;
}

Pixel PictureGetNextColor(Pixel p, int n)
{
	int i;
	XColor c;

	if (n >= 0)
		n = 1;
	else
		n = -1;

	if (Pct == NULL)
	{
		return p;
	}
	for(i=0; i<PColorLimit; i++)
	{
		if (Pct[i].color.pixel == p)
		{
			if (i == 0 && n < 0)
			{
				c = Pct[PColorLimit-1].color;
				alloc_color_in_pct(&c, PColorLimit-1);
				return Pct[PColorLimit-1].color.pixel;
			}
			else if (i == PColorLimit-1 && n > 0)
			{
				c = Pct[0].color;
				alloc_color_in_pct(&c, 0);
				return Pct[0].color.pixel;
			}
			else
			{
				c = Pct[i+n].color;
				alloc_color_in_pct(&c, i+n);
				return Pct[i+n].color.pixel;
			}
		}
	}
	return p;
}

/* Replace the color in my_color by the closest matching color
   from base_table */
void PictureReduceColorName(char **my_color)
{
	int index;
	XColor rgb;          /* place to calc rgb for each color in xpm */

	if (!XpmSupport)
		return;

	if (!strcasecmp(*my_color,"none")) {
		return; /* do not substitute the "none" color */
	}

	if (!XParseColor(Pdpy, Pcmap, *my_color, &rgb))
	{
		fprintf(stderr,"color_to_rgb: can't parse color %s\n",
			*my_color);
	}
	index = get_color_index(rgb.red,rgb.green,rgb.blue, False);
	/* Finally: replace the color string by the newly determined color
	 * string */
	free(*my_color);                    /* free old color */
	/* area for new color */
	*my_color = safemalloc(8);
	sprintf(*my_color,"#%x%x%x",
		Pct[index].color.red >> 8,
		Pct[index].color.green >> 8,
		Pct[index].color.blue >> 8); /* put it there */
	return;
}

Bool PictureDitherByDefault(void)
{
	if (Pct != NULL)
	{
		return True;
	}
	return False;
}

Bool PictureUseBWOnly(void)
{
	if (Pdepth < 2 || (PStrictColorLimit && PColorLimit == 2))
	{
		return True;
	}
	return False;
}

int PictureInitColors(
	int call_type, Bool init_color_limit, PictureColorLimitOption *opt,
	Bool use_my_color_limit, Bool init_dither)
{
	Bool dither_ok = False;

	switch (Pvisual->class)
	{
	case DirectColor:
		/* direct colors is more or less broken */
		decompose_mask(
			Pvisual->red_mask, &Pcsi.red_shift,
			&Pcsi.red_prec);
		decompose_mask(
			Pvisual->green_mask, &Pcsi.green_shift,
			&Pcsi.green_prec);
		decompose_mask(
			Pvisual->blue_mask, &Pcsi.blue_shift,
			&Pcsi.blue_prec);
		Pcsi.alloc_color_no_limit = alloc_color_proportion;
		Pcsi.alloc_color = alloc_color_proportion;
		Pcsi.alloc_color_dither = alloc_color_proportion_dither;
		Pcsi.free_colors_no_limit = NULL;
		Pcsi.free_colors = NULL;
		PColorLimit = 0;
		break;
	case TrueColor:
		decompose_mask(
			Pvisual->red_mask, &Pcsi.red_shift,
			&Pcsi.red_prec);
		decompose_mask(
			Pvisual->green_mask, &Pcsi.green_shift,
			&Pcsi.green_prec);
		decompose_mask(
			Pvisual->blue_mask, &Pcsi.blue_shift,
			&Pcsi.blue_prec);
		Pcsi.alloc_color_no_limit = alloc_color_proportion;
		Pcsi.alloc_color = alloc_color_proportion;
		Pcsi.free_colors_no_limit = NULL;
		Pcsi.free_colors = NULL;
		PColorLimit = 0;
		if (init_dither && (Pdepth == 15 || Pdepth == 16))
		{
			dither_ok = my_dither_depth_15_16_init();
		}
		if (dither_ok)
		{
			Pcsi.alloc_color_dither = alloc_color_proportion_dither;
		}
		else
		{
			Pcsi.alloc_color_dither = NULL;
		}
		break;
	case StaticColor:
		if (0 && Pvisual->red_mask != 0 && Pvisual->green_mask != 0 &&
		    Pvisual->blue_mask != 0)
		{
			decompose_mask(
				Pvisual->red_mask, &Pcsi.red_shift,
				&Pcsi.red_prec);
			decompose_mask(
				Pvisual->green_mask, &Pcsi.green_shift,
				&Pcsi.green_prec);
			decompose_mask(
				Pvisual->blue_mask, &Pcsi.blue_shift,
				&Pcsi.blue_prec);
			Pcsi.alloc_color_no_limit = alloc_color_proportion;
			Pcsi.alloc_color = alloc_color_proportion;
			PColorLimit = 0;
		}
		else
		{
			if (init_color_limit)
			{
				Pcsi.alloc_color = alloc_color_in_table;
				Pcsi.alloc_color_dither =
					alloc_color_in_table_dither;
				Pcsi.alloc_color_no_limit = alloc_color_x;
				init_static_colors_table();
			}
			else
			{
				Pcsi.alloc_color = alloc_color_x;
				Pcsi.alloc_color_dither = NULL;
				Pcsi.alloc_color_no_limit = alloc_color_x;
			}
		}
		Pcsi.free_colors_no_limit = NULL;
		Pcsi.free_colors = NULL;
		break;
	case StaticGray:
		/* FIXME: we assume that we have a regular grey ramp */
		if (0)
		{
			Pcsi.alloc_color_no_limit = alloc_color_proportion_grey;
			Pcsi.alloc_color = alloc_color_proportion;
			PColorLimit = 0;
		}
		else
		{
			if (init_color_limit)
			{
				Pcsi.alloc_color = alloc_color_in_table;
				Pcsi.alloc_color_dither =
					alloc_color_in_table_dither;
				Pcsi.alloc_color_no_limit = alloc_color_x;
				init_static_colors_table();
			}
			else
			{
				Pcsi.alloc_color = alloc_color_x;
				Pcsi.alloc_color_dither = NULL;
				Pcsi.alloc_color_no_limit = alloc_color_x;
			}
		}
		Pcsi.free_colors_no_limit = NULL;
		Pcsi.free_colors = NULL;
		break;
	case PseudoColor:
	case GrayScale:
	default:
		Pcsi.alloc_color_no_limit = alloc_color_dynamic_no_limit;
		Pcsi.free_colors_no_limit = free_colors_x;
		break;
	}

	if (!(Pvisual->class & 1))
	{
		/* static classes */
		PUseDynamicColors = 0;
		if (call_type == PICTURE_CALLED_BY_FVWM &&
		    getenv("FVWM_COLORTABLE_TYPE") != NULL)
		{
			flib_putenv("FVWM_COLORTABLE_TYPE", "");
		}
		return PColorLimit;
	}

	/* dynamic classes */

	if (!Pdefault && Pvisual->class == DirectColor)
	{
		PColorLimit = 0;
		PUseDynamicColors = 0;
		alloc_direct_colors(0, use_my_color_limit);
		if (call_type == PICTURE_CALLED_BY_FVWM &&
		    getenv("FVWM_COLORTABLE_TYPE") != NULL)
		{
			flib_putenv("FVWM_COLORTABLE_TYPE", "");
		}
		return 0;
	}


	if (init_color_limit)
	{
		Pcsi.alloc_color = alloc_color_in_table;
		Pcsi.alloc_color_dither = alloc_color_in_table_dither;
		PictureAllocColorTable(opt, call_type, use_my_color_limit);
		if (PUseDynamicColors)
		{
			Pcsi.free_colors = free_colors_in_table;
		}
		else
		{
			Pcsi.free_colors = NULL;
		}
	}
	else
	{
		Pcsi.alloc_color = alloc_color_dynamic_no_limit;
		Pcsi.free_colors = free_colors_x;
		Pcsi.alloc_color_dither = NULL;
	}
	return PColorLimit;
}

void PicturePrintColorInfo(int verbose)
{
	unsigned long nbr_of_colors = 1 << Pdepth;

	fprintf(stderr, "fvwm info on colors\n");
	fprintf(stderr, "  Visual ID: 0x%x, Default?: %s, Class: ",
		(int)(Pvisual->visualid),
		(Pdefault)? "Yes":"No");
	if (Pvisual->class == TrueColor)
	{
		fprintf(stderr,"TrueColor");
	}
	else if (Pvisual->class == PseudoColor)
	{
		fprintf(stderr,"PseudoColor");
	}
	else if (Pvisual->class == DirectColor)
	{
		fprintf(stderr,"DirectColor");
	}
	else if (Pvisual->class == StaticColor)
	{
		fprintf(stderr,"StaticColor");
	}
	else if (Pvisual->class == GrayScale)
	{
		fprintf(stderr,"GrayScale");
	}
	else if (Pvisual->class == StaticGray)
	{
		fprintf(stderr,"StaticGray");
	}
	fprintf(stderr, "\n");
	fprintf(stderr, "  Depth: %i, Number of colors: %lu",
		Pdepth, (unsigned long)nbr_of_colors);
	if (Pct != NULL)
	{
		fprintf(stderr,"\n  Pallet with %i colors", PColorLimit);
		if (Pvisual->class & 1)
		{
			fprintf(stderr,", Number of free colors: %i\n",
				get_nbr_of_free_colors((1 << Pdepth)));
			fprintf(stderr,
				"  Auto Detected: %s, Strict: %s, Allocated: %s,"
				" Dynamic: %s\n",
				(Pcsi.pre_allocated_pallet)? "Yes":"No",
				(PStrictColorLimit)? "Yes":"No",
				(PAllocTable)? "Yes":"No",
				(PUseDynamicColors)? "Yes":"No");
		}
		else
		{
			fprintf(stderr," (default colormap)\n");
		}
		if (PColorLimit <= 256)
		{
			int i;
			int count = 0;
			int count_alloc = 0;

			if (verbose)
			{
				fprintf(stderr,"  The fvwm colors table:\n");
			}
			for (i = 0; i < PColorLimit; i++)
			{
				if (verbose)
				{
					fprintf(
						stderr,
						"    rgb:%.3i/%.3i/%.3i\t%lu\n",
						Pct[i].color.red >> 8,
						Pct[i].color.green >> 8,
						Pct[i].color.blue >> 8,
						Pct[i].alloc_count);
				}
				if (Pct[i].alloc_count)
				{
					count++;
				}
			}
			if ((Pvisual->class & 1) && Pac != NULL)
			{
				if (verbose)
				{
					fprintf(stderr,"  fvwm colors not in"
						" the table:\n");
				}
				for(i=0; i < nbr_of_colors; i++)
				{
					int j = 0;
					Bool found = False;

					if (!Pac[i].alloc_count)
						continue;
					while(j < PColorLimit && !found)
					{
						if (i == Pct[j].color.pixel)
						{
							found = True;
						}
						j++;
					}
					if (found)
						continue;
					count_alloc++;
					if (verbose)
					{
						fprintf(
							stderr,
							"    rgb:"
							"%.3i/%.3i/%.3i\t%lu\n",
							Pac[i].color.red >> 8,
							Pac[i].color.green >> 8,
							Pac[i].color.blue >> 8,
							Pac[i].alloc_count);
					}
				}
				if (verbose && count_alloc == 0)
				{
					if (verbose)
					{
						fprintf(stderr,"    None\n");
					}
				}
			}
			if (Pvisual->class & 1)
			{
				fprintf(stderr,
					"  Number of colours used by fvwm:\n");
				fprintf(stderr,
					"    In the table: %i\n", count);
				fprintf(
					stderr, "    Out of the table: %i\n",
					count_alloc);
				fprintf(stderr,
					"    Total: %i\n", count_alloc+count);
			}
		}
	}
	else
	{
		if (Pvisual->class == DirectColor)
		{
			fprintf(stderr, ", Pseudo Pallet with: %i colors\n",
				(1 << Pcsi.red_prec)*(1 << Pcsi.green_prec)*
				(1 << Pcsi.blue_prec));
		}
		else
		{
			fprintf(stderr, ", No Pallet (static colors)\n");
		}
		fprintf(stderr, "  red: %i, green: %i, blue %i\n",
			1 << Pcsi.red_prec, 1 << Pcsi.green_prec,
			1 << Pcsi.blue_prec);
		if (verbose && Pdepth <= 8)
		{
			if (Pvisual->class == DirectColor)
			{
				fprintf(stderr, "  Colormap:\n");
			}
			else
			{
				fprintf(stderr,
					"  Static Colormap used by fvwm:\n");
			}
			print_colormap(Pcmap);
		}
	}

	if (Pdepth <= 8 && verbose >= 2)
	{
		fprintf(stderr,"\n  Default Colormap:\n");
		print_colormap(DefaultColormap(Pdpy,DefaultScreen(Pdpy)));
	}
}


syntax highlighted by Code2HTML, v. 0.9.1