#import "../ImageOpr.h"
#import <appkit/Application.h>
#import <appkit/publicWraps.h>
#import <appkit/NXBitmapImageRep.h>
#import <appkit/Control.h>
#import <appkit/Panel.h>
#import <appkit/TextField.h>
#import <stdio.h>
#import <stdlib.h>
#import <string.h>
#import <libc.h>
#import <streams/streams.h>
#import "../TVController.h"
#import "../ToyWin.h"
#import "../ToyWinEPS.h"
#import "../ToyView.h"
#import "../common.h"
#import "../getpixel.h"
#import "imageOperation.h"


@implementation ImageOpr (ColorReplace)


#define  CLR_THRESHOLD	(32 * 32 * 3)
#define  BRT_THRESHOLD	(64 * 3)
#define  MONO_THRESHOLD	128

static BOOL isMono;
static const int *origClr;
static int origbrite, cthresh, bthreshm, bthreshp;

static void initNearColor(const int *orig, float comparison, int cnum)
{
    origClr = orig;
    if (cnum < 3) {
	isMono = YES;
	origbrite = orig[0];
	bthreshm = orig[0] - MONO_THRESHOLD * comparison;
	bthreshp = orig[0] + MONO_THRESHOLD * comparison;
    }else {
	isMono = NO;
	origbrite = Bright255(orig[RED], orig[GREEN], orig[BLUE]);
	bthreshm = origbrite - BRT_THRESHOLD * comparison;
	bthreshp = origbrite + BRT_THRESHOLD * comparison;
	cthresh = CLR_THRESHOLD * comparison;
    }
}

static BOOL isNearColor(const int *elm)
{
	int i, brt;
	double dif, d, fr;

	if (isMono)
		return (elm[0] < bthreshp && elm[0] > bthreshm);

	brt = Bright255(elm[RED], elm[GREEN], elm[BLUE]);
	if (brt > bthreshp || brt < bthreshm)	/* compare brightness */
		return NO;
	if (brt == 0 || brt == 255 * 3)
		return YES;	/* White/Black within allowed brightness */
	fr = (float)origbrite / brt;
	for (dif = 0, i = 0; i < 3; i++) {
		d = fr * elm[i] - origClr[i];
		dif += d * d;
	}
	if (dif < cthresh)
		return YES;
	fr = (255.0 * 3 - origClr[GREEN]) / (255.0 * 3 - elm[GREEN]);
	for (dif = 0, i = 0; i < 3; i++) {
		d = fr * (255.0 - elm[i]) - (255.0 - origClr[i]);
		dif += d * d;
	}
	if (dif < cthresh)
		return YES;
	return NO;
}

static commonInfo *makeClrChangeMap(const int *origclr, const int *newclr,
	float comparison, BOOL uniform, BOOL outside,
	commonInfo *cinf, NXRect *selrect,
	const unsigned char *map[], unsigned char *newmap[])
{
	commonInfo *newinf = NULL;
	int pn, alp, cn;
	int x, y, i;
	int xorg=0, yorg=0, xend=0, yend=0;

	newmap[0] = NULL;
	if (selrect == NULL
		|| selrect->size.width <= 0 || selrect->size.height <= 0
		|| (selrect->size.width >= cinf->width &&
			selrect->size.height >= cinf->height))
		selrect = NULL;
	else {
		xorg = selrect->origin.x;
		xend = selrect->size.width + xorg - 1;
		yorg = cinf->height -
			(selrect->origin.y + selrect->size.height);
		yend = cinf->height - selrect->origin.y - 1;
	}
	if ((newinf = (commonInfo *)malloc(sizeof(commonInfo))) == NULL)
		goto ErrEXIT;
	*newinf = *cinf;
	newinf->palette = NULL;
	newinf->palsteps = 0;
	newinf->isplanar = YES;
	newinf->bits = 8;
	newinf->xbytes = newinf->width;
	if (cinf->alpha && origclr[ALPHA] == AlphaTransp && selrect == NULL)
		newinf->alpha = NO;
	if (newclr[ALPHA] == AlphaTransp)
		newinf->alpha = YES;
	if (cinf->numcolors == 1) {
		if (newclr[RED] != newclr[GREEN]
				|| newclr[RED] != newclr[BLUE]) {
			newinf->numcolors = 3;
			newinf->cspace = NX_RGBColorSpace;
		}else
			newinf->cspace = NX_OneIsWhiteColorSpace;
	}
	pn = cn = (newinf->numcolors == 1) ? 1 : 3;
	if (newinf->alpha) alp = pn++;
	else alp = 0;
	initNearColor(origclr, comparison, cn);
	if (allocImage(newmap, newinf->width, newinf->height, 8, pn))
		goto ErrEXIT;
	if (initGetPixel(cinf) != 0)
		goto ErrEXIT;
	resetPixel(map, 0);
	for (y = 0; y < newinf->height; y++) {
	    int elm[MAXPLANE], gen[MAXPLANE];
	    const int *cp;
	    BOOL selflag = selrect ? (yorg <= y && y <= yend) : NO;
	    int ptr = newinf->width * y;
	    for (x = 0; x < newinf->width; x++, ptr++) {
		getPixel(&elm[RED], &elm[GREEN], &elm[BLUE], &elm[ALPHA]);
		if (selrect == NULL ||
			(outside ^ (selflag && xorg <= x && x <= xend))) {
		    if (origclr[ALPHA] == AlphaTransp)
			cp = (elm[ALPHA] == AlphaTransp) ? newclr : elm;
		    else if (elm[ALPHA] == AlphaTransp)
			cp = elm;	/* origclr[ALPHA] != AlphaTransp */
		    else if (comparison == 0.0) {
			for (i = 0; i < cn; i++)
				if (origclr[i] != elm[i]) break;
			cp = (i >= cn) ? newclr : elm;
		    }else if (isNearColor(elm)) {
				if (uniform) cp = newclr;
				else {
				    int d;
				    for (i = 0; i < cn; i++) {
					d = elm[i] - origclr[i] + newclr[i];
					gen[i] = (d>255)? 255: ((d<0)? 0: d);
				    }
				    if (alp) gen[ALPHA] = newclr[ALPHA];
				    cp = gen;
				}
		    }else
		    	cp = elm;
		}else
		    cp = elm;
		for (i = 0; i < cn; i++)
		    newmap[i][ptr] = cp[i];
		if (alp)
		    newmap[alp][ptr] = cp[ALPHA];
	    }
	}
	return newinf;

ErrEXIT:
	if (newinf)
		free((void *)newinf);
	if (newmap[0]) free((void *)newmap[0]);
	return NULL;
}


- colorChange:(int *)origclr to:(int *)newclr with:(float)comparison
		uniformly:(BOOL)uniform area:(BOOL)outside
{
	ToyWin	*tw, *newtw = nil;
	ToyView	*tv = nil;
	commonInfo	*cinf, *newinf;
	const char *fnam;
	char fn[256];
	NXBitmapImageRep *rep;
	unsigned char *map[MAXPLANE], *newmap[MAXPLANE];
	int i;

	for (i = 0; i < 4; i++)
		if (origclr[i] != newclr[i]) break;
	if (i >= 4 /* Same Color */
		|| (tw = [theController keyWindow]) == nil) {
		NXBeep();
		return self;
	}
	tv = [tw toyView];
	cinf = [tv commonInfo];
	if (!cinf->alpha && origclr[ALPHA] == AlphaTransp) {
		NXBeep();
		return self;
	}
	fnam = [tw filename];
	if (![self checkCommonInfo:cinf filename:fnam])
		return self;
	sprintf(fn, "%s(ColorChange)", fnam);
	rep = (NXBitmapImageRep *)[[tv image] bestRepresentation];
	[rep getDataPlanes: map];
	newinf = makeClrChangeMap(origclr, newclr, comparison,
		uniform, outside, cinf, [tv selectedScaledRect], map, newmap);
	if (newinf == NULL) {
		warnAlert(fnam, Err_MEMORY);
		return self;
	}
	newtw = [[ToyWin alloc] init:tw by:ColorChange];
	[newtw initLocateWindow:fn width:newinf->width height:newinf->height];
	[newtw makeComment: newinf from: cinf];
	[newtw drawView:newmap info: newinf];

	[theController newWindow: newtw];
	return self;
}

@end
