#import  "Posterizer.h"
#import  <appkit/TextField.h>
#import  <stdio.h>
#import  <stdlib.h>
#import  <string.h>
#import "../common.h"
#import "../getpixel.h"
#import "imageOperation.h"

#define  CellMAX(n)	((n) >> 2)
#define  DiffHUGE	(256 * 256 * 3)
#define  AvrMAX		1000
#define  AlphaMark	255

typedef  int	area_t;			/* 32 bits */
typedef  unsigned char	brite_t;

typedef struct _area_cell {
	long	rgb[3];
	long	count;
	long	alias;
} area_cell;


@implementation Posterizer

static commonInfo *cinf;
static int cnum;
static int cell_max;
static unsigned char *planes[MAXPLANE];	/* faster than **planes */
/* work memory */
static area_t *area;
static brite_t *brite;
static area_cell *acell;


static int alink(int idx)
{
	int	i, x;
	area_cell	*ptr;

	for (x = idx; x < cell_max && acell[x].count == 0; x = acell[x].alias)
		;
	for (i = idx; i != x; ) {
		i = (ptr = &acell[i])->alias;
		ptr->alias = x;
	}
	return x;
}

- initWith:(commonInfo *)info newmap:(unsigned char **)newmap
{
	long	allpix;
	int	i, pnum;

	cinf = info;
	area = NULL;
	brite = NULL;
	acell = NULL;
	pnum = cnum = cinf->numcolors;
	if (cinf->alpha) pnum++;
	if (allocImage(newmap, cinf->width, cinf->height, 8, pnum))
		return nil;
	for (i = 0; i < MAXPLANE; i++)
		planes[i] = newmap[i];
	allpix = cinf->width * cinf->height;
	cell_max = CellMAX(allpix);
	acell = (area_cell *)malloc(cell_max * sizeof(area_cell));
	area = (area_t *)malloc(allpix * sizeof(area_t));
	brite = (brite_t *)malloc(allpix * sizeof(brite_t));
	if (area == NULL || acell == NULL || brite == NULL)
		return nil;
	msgtext = nil;
	return self;
}

- free
{
	if (area) {
		free((void *)area);
		area = NULL;
	}
	if (acell) {
		free((void *)acell);
		acell = NULL;
	}
	if (brite) {
		free((void *)brite);
		brite = NULL;
	}
	[super free];
	return nil;
}

- setMessageText: text
{
	msgtext = text;
	return self;
}

static int distance(int r1, int g1, int b1, int r2, int g2, int b2)
{
	int	r = r1 - r2;
	int	g = g1 - g2;
	int	b = b1 - b2;

	return ((r * r + g * g + b * b) / 3);
}

static void smooth(int brdiff)
{
	int	tim, i, j, x, y, idx;
	int	cnt, val, sum, d;
	int	index[8];
	index[0] = - cinf->width - 1;
	index[1] = - cinf->width;
	index[2] = - cinf->width + 1;
	index[3] = -1;
	index[4] = 1;
	index[5] = cinf->width - 1;
	index[6] = cinf->width;
	index[7] = cinf->width + 1;

	for (tim = 0; tim < 2; tim++)
	    for (y = 1; y < cinf->height - 1; y++) {
		    idx = y * cinf->width + 1;
		    for (x = 1; x < cinf->width - 1; x++, idx++) {
			cnt = 1;
			sum = val = brite[idx];
			if (val == AlphaMark)
				continue;
			for (i = 0; i < 8; i++) {
				if ((j = brite[idx + index[i]]) == AlphaMark)
					continue;
				d = j - val;
				if (d > - brdiff && d < brdiff)
					sum += j, cnt++;
			}
			if (cnt > 1)
				brite[idx] = sum / cnt;
		    }
	    }
}

static int neighbor(int nx, int idx, int phase)
{
	int	v, n, ap;
	int	va, vb;
	long	*lp;
	area_cell *aptr;

	if (cinf->alpha &&
		(planes[cnum][nx] == AlphaTransp
			|| planes[cnum][idx] == AlphaTransp))
	if (phase == 1 && area[nx] == area[idx])
		return DiffHUGE;
	if (cnum > 1) {
		va = distance(planes[0][nx], planes[1][nx], planes[2][nx],
			planes[0][idx], planes[1][idx], planes[2][idx]);
	}else {
		v = (int)planes[0][nx] - (int)planes[0][idx];
		va = v * v;
	}

	if ((ap = area[nx]) >= cell_max) /* Alpha */
		return DiffHUGE;
	aptr = &acell[alink(ap)];
	lp = aptr->rgb;
	if (phase == 0 && (n = aptr->count) > 1) {
	    if (cnum > 1) {
		vb = distance(lp[0] / n, lp[1] / n, lp[2] / n,
			planes[0][idx], planes[1][idx], planes[2][idx]);
	    }else {
		v = lp[0] / n - (int)planes[0][idx];
		vb = v * v;
	    }
	}else {
	    if (cnum > 1) {
		vb = distance(lp[0], lp[1], lp[2],
			planes[0][idx], planes[1][idx], planes[2][idx]);
	    }else {
		v = lp[0] - (int)planes[0][idx];
		vb = v * v;
	    }
	}

	return (vb > va) ? vb : va;
}

/* Local Method */
- (BOOL) segment: (int)brdiff : (int)diffc
{
	int	i, j, x, y, base, up, rp;
	int	acp, idx, alp, scandir;
	int	elm[MAXPLANE];
	area_cell *aptr, *bptr;
	long	*lp;
	long	diffcolor, difx, dify;
	unsigned char br[256];

	for (i = 0, j = 0; i < 256; ) {
		br[i] = j;
		if (++i > j) j += brdiff;
	}
	diffcolor = diffc * diffc;
	alp = (cinf->alpha) ? cnum : 0;
	acp = 0;
	idx = 0;
	for (y = 0; y < cinf->height; y++) {
	    for (x = 0; x < cinf->width; x++, idx++) {
		getPixel(&elm[0], &elm[1], &elm[2], &elm[3]);
		for (i = 0; i < cnum; i++)
			planes[i][idx] = elm[i];
		if (alp) {
			planes[alp][idx] = elm[ALPHA];
			if (elm[ALPHA] == AlphaTransp) {
				brite[idx] = AlphaMark;	/* 255 */
				continue;
			}
		}
		j = (cnum == 1) ? elm[0] : Bright255(elm[0], elm[1], elm[2]);
		brite[idx] = (j >= 255) ? 254 : j;
	    }
	}
	smooth(brdiff);

	idx = 0;
	for (y = 0; y < cinf->height; y++)
	    for (x = 0; x < cinf->width; x++, idx++)
		brite[idx] = br[brite[idx]];

	scandir = 1;
	for (y = 0; y < cinf->height; y++) {
	    base = y * cinf->width;
	    for (x = (scandir > 0) ? 0 : (cinf->width - 1);
			x >= 0 && x < cinf->width; x += scandir) {
		idx = base + x;
		if (alp && planes[alp][idx] == AlphaTransp) {
			area[idx] = cell_max;	/* mark as ALPHA */
			continue;
		}
		rp = ((j = x - scandir) < 0 || j >= cinf->width)
			? -1 : (idx - scandir);
		up = (y == 0) ? -1 : (idx - cinf->width);
		difx = (rp < 0 || brite[rp] != brite[idx])
			? DiffHUGE : neighbor(rp, idx, 0);
		dify = (up < 0 || brite[up] != brite[idx])
			? DiffHUGE : neighbor(up, idx, 0);
		if (difx > diffcolor && dify > diffcolor) {
			/* isolated pixel */
			aptr = &acell[area[idx] = acp];
			lp = aptr->rgb;
			for (i = 0; i < cnum; i++)
				lp[i] = planes[i][idx];
			aptr->count = 1;
			if (++acp >= cell_max) /* Too Many Colors */
				return NO;
		}else {
			if (difx <= diffcolor && dify <= diffcolor
			&& (i = alink(area[rp])) != (j = alink(area[up]))
				&& neighbor(rp, up, 0) <= diffcolor) {
				aptr = &acell[i];
				bptr = &acell[j];
				bptr->count += aptr->count;
				aptr->count = 0;
				aptr->alias = j;
				for (i = 0; i < cnum; i++)
					bptr->rgb[i] += aptr->rgb[i];
			}else
				j = alink(area[(difx < dify)? rp : up]);
			area[idx] = j;
			aptr = &acell[j];
			if (aptr->count < AvrMAX) {
				lp = aptr->rgb;
				for (i = 0; i < cnum; i++)
					lp[i] += planes[i][idx];
				aptr->count++;
			}
		}
	    } /* x */
	    scandir = (scandir > 0) ? -1 : 1;
	}

	for (j = 0; j < acp; j++) {
		long n;
		if ((n = (aptr = &acell[j])->count) > 1) {
			lp = aptr->rgb;
			for (i = 0; i < cnum; i++)
				lp[i] /= n;
		}
	}
	return YES;
}


- posterize:(unsigned char **)map with:(float)diff : (float)ctrl
{
	int	i, x, y, idx;
	long	*lp;
	int	diffc, brdiff;
	area_cell *aptr;
	float	w;

	w = 0.25 + (diff * 0.75);
	for (w *= w; ; w += 0.05) {
		brdiff = w * 64;
		diffc = brdiff * (0.1 + (ctrl * 0.9));
		if (diffc < 4) diffc = 4;
		if (msgtext) {
			char msgbuf[80];
			sprintf(msgbuf, "Posterizing: %d:%d", brdiff, diffc);
			[msgtext setStringValue: msgbuf];
			NXPing();
		}
		resetPixel(map, 0);
		if ([self segment: brdiff : diffc]) break;
	}
	if (msgtext) {
		[msgtext setStringValue: "Painting..."];
		NXPing();
	}

	idx = 0;
	for (y = 0; y < cinf->height; y++) {
	    for (x = 0; x < cinf->width; x++, idx++) {
		aptr = &acell[alink(area[idx])];
		if (aptr->count > 1) {
			lp = aptr->rgb;
			for (i = 0; i < cnum; i++)
				planes[i][idx] = lp[i];
		}
	    }
	}
	return self;
}

@end
