
/* 
 * Argyll Color Correction System
 * ChromCast up filter test code.
 *
 * Author: Graeme W. Gill
 * Date:   28/8/2014
 *
 * Copyright 2014 Graeme W. Gill
 * All rights reserved.
 *
 * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
 * see the License2.txt file for licencing details.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <sys/types.h>
#include <time.h>
#include "copyright.h"
#include "aconfig.h"
#ifndef SALONEINSTLIB
#include "numlib.h"
#else
#include "numsup.h"
#endif
#include "yajl.h"
#include "conv.h"
#include "base64.h"
#include "ccmdns.h"
#include "ccpacket.h"
#include "ccmes.h"
#include "yajl.h"
#include "ccast.h"

#define DO_WEIGHTING
#define SUM_CONSTRAINT

//#define VERT 1		/* 1 for vertical */

#ifndef DBL_PI
# define DBL_PI         3.1415926535897932384626433832795
#endif

double lanczos3(double wi, double x) {
	double y;

	x = fabs(1.0 * x/wi);
	if (x >= 3.0)
		return 0.0; 
	if (x < 1e-6)
		return 1.0;
	y = sin(DBL_PI * x)/(DBL_PI * x) * sin(DBL_PI * x/3.0)/(DBL_PI * x/3.0);

	return y;
}

double lanczos2(double wi, double x) {
	double y;

	x = fabs(1.0 * x/wi);
	if (x >= 2.0)
		return 0.0; 
	if (x < 1e-6)
		return 1.0;
	y = sin(DBL_PI * x)/(DBL_PI * x) * sin(DBL_PI * x/2.0)/(DBL_PI * x/2.0);

	return y;
}

double in[2][72] = {
{	// Horizontal 
255, 0, 0, 0, 0, 0, 0, 0, 0,
255, 0, 0, 0, 0, 0, 0, 0, 0,
254, 0, 0, 0, 0, 0, 0, 0, 0,
254, 0, 0, 0, 0, 0, 0, 0, 0,
253, 0, 0, 0, 0, 0, 0, 0, 0,
253, 0, 0, 0, 0, 0, 0, 0, 0,
252, 0, 0, 0, 0, 0, 0, 0, 0,
252, 0, 0, 0, 0, 0, 0, 0, 0
}, { // Vertical slice input target
255, 0, 0, 0, 0, 0, 0, 0, 0,
255, 0, 0, 0, 0, 0, 0, 0, 0,
254, 0, 0, 0, 0, 0, 0, 0, 0,
254, 0, 0, 0, 0, 0, 0, 0, 0,
253, 0, 0, 0, 0, 0, 0, 0, 0,
253, 0, 0, 0, 0, 0, 0, 0, 0,
252, 0, 0, 0, 0, 0, 0, 0, 0,
252, 0, 0, 0, 0, 0, 0, 0, 0
} };
 
#ifdef NEVER
double out[2][] = {
{	// Horizontal
170, 110, 23, 5, 15, 17, 16, 16, 16, 17, 10, 6, 57,
151, 153, 61, 7, 10, 16, 16, 16, 16, 17, 15, 5, 22,
107, 169, 109, 23, 5, 15, 17, 16, 16, 16, 17, 10, 6, 57,
150, 152, 61, 7, 10, 16, 16, 16, 16, 17, 15, 5, 22,
107, 168, 109, 23, 5, 15, 17, 16, 16, 16, 17, 10, 6, 56,
149, 151, 61, 7, 10, 16, 16, 16, 16, 17, 15, 6, 22,
106, 167, 108, 23, 5, 15, 17, 16, 16, 16, 17, 10, 6, 56,
148, 150
},
{ // Vertical slice target output
235, 100, 0, 16, 16, 16, 16, 16, 16, 16, 20, 1, 13,
191, 194, 19, 0, 20, 16, 16, 16, 16, 16, 16, 16, 0, 95,
234, 99, 0, 16, 16, 16, 16, 16, 16, 16, 20, 1, 13,
190, 194, 19, 0, 20, 16, 16, 16, 16, 16, 16, 16, 0, 94,
233, 99, 0, 16, 16, 16, 16, 16, 16, 16, 20, 1, 13,
189, 193, 19, 0, 20, 16, 16, 16, 16, 16, 16, 16, 0, 94,
232, 99, 0, 16, 16, 16, 16, 16, 16, 16, 20, 1, 13,
188, 192
}
};
#else
//#define N1 -9
//#define N2 -8
#define N1 0
#define N2 0

//#define N3 1
#define N4 0

double out[2][96] = {
{	// Horozontal
170, 110, 23, 5, 15, 17, 16, 16, 16, 17, 10, 6, 57,
151, 153, 61, 7, 10, 16, 16, 16, 16, 17, 15, 5, 22,
107, 169, 109, 23, 5, 15, 17, 16, 16, 16, 17, 10, 6, 57,
150, 152, 61, 7, 10, 16, 16, 16, 16, 17, 15, 5, 22,
107, 168, 109, 23, 5, 15, 17, 16, 16, 16, 17, 10, 6, 56,
149, 151, 61, 7, 10, 16, 16, 16, 16, 17, 15, 6, 22,
106, 167, 108, 23, 5, 15, 17, 16, 16, 16, 17, 10, 6, 56,
148, 150
},
{	// Vertical slice target output
235, 100, N2, 16, 16, 16, 16, 16, 16, 16, 20,  1,  13,
191, 194, 19, N4,  20, 16, 16, 16, 16, 16, 16, 16, N1, 95,
234, 99,  N2, 16, 16, 16, 16, 16, 16, 16, 20,  1,  13,
190, 194, 19, N4,  20, 16, 16, 16, 16, 16, 16, 16, N1, 94,
233, 99,  N2, 16, 16, 16, 16, 16, 16, 16, 20,  1,  13,
189, 193, 19, N4,  20, 16, 16, 16, 16, 16, 16, 16, N1, 94,
232, 99,  N2, 16, 16, 16, 16, 16, 16, 16, 20,  1,  13,
188, 192
}
};
#endif

// Computed the input to output filters.
// There will be two phases, depending on whether
// the input pixel has an even or odd address.
// Although in this case they seem like they
// almost interleave, they are actually mirror
// images with offset, so it's easier to keep them
// separate, rather than trying to figure the offset out.
// The index is the output pixel around the closest one
// to the scaled input pixel - ie. floor(in * 1.5 + 0.5)
#define FWIDTH 6
#define NWIDTH (2 * FWIDTH + 1)

double filt_v[2][2][NWIDTH];
double filt_vx[2][2][NWIDTH];			/* max */
double filt_vn[2][2][NWIDTH];			/* min */

double *filt[2][2] = { { &filt_v[0][0][FWIDTH], &filt_v[0][1][FWIDTH] },
                       { &filt_v[1][0][FWIDTH], &filt_v[1][1][FWIDTH] } };

double *filtx[2][2] = { { &filt_vx[0][0][FWIDTH], &filt_vx[0][1][FWIDTH] },
                        { &filt_vx[1][0][FWIDTH], &filt_vx[1][1][FWIDTH] } };

double *filtn[2][2] = { { &filt_vn[0][0][FWIDTH], &filt_vn[0][1][FWIDTH] },
                        { &filt_vn[1][0][FWIDTH], &filt_vn[1][1][FWIDTH] } };

//int fneg[2] = { -5, -4 };		/* Negative index range (inclusive) */
//int fpos[2] = {  5,  3 };		/* Positive index range (inclusive) */
int fneg[2][2] = { { -4, -4 },		/* Negative index range (inclusive) */
                   { -4, -4 } };
int fpos[2][2] = { {  4,  3 },		/* Positive index range (inclusive) */
                   {  4,  3 } };

/* Weightings [horiz/vert][phase] */
double filtw_v[2][2][NWIDTH] = {
	  /* -6,  -5,  -4,  -3,  -2,  -1,     0,     +1,  +2,   +3,  +4,  +5,  +6 */
//	{ {  1.0, 1.0, 1.0, 5.0, 3.0, 42.0,  80.0,  43.0, 3.0, 5.0, 1.0, 1.0, 1.0 },
//	  {  1.0, 1.0, 3.0, 4.0, 19.0, 62.0,  62.0, 21.0,  4.0, 3.0, 1.0, 1.0, 1.0 } },
	{ {  1.0, 1.0, 1.0, 1.0, 1.0, 1.0,  1.0,  1.0, 1.0, 1.0, 1.0, 1.0, 1.0 },
	  {  1.0, 1.0, 1.0, 1.0, 1.0, 1.0,  1.0, 1.0,  1.0, 1.0, 1.0, 1.0, 1.0 } },
	  /* -6,  -5,  -4,  -3,  -2,  -1,     0,     +1,  +2,   +3,  +4,  +5,  +6 */
//	{ {  1.0, 1.0, 1.0, 3.0, 17.0, 36.0, 100.0, 40.0, 15.0, 1.0, 3.0, 1.0, 1.0 },
//	  {  1.0, 1.0, 1.0, 8.0, 2.0,  80.0,  81.0,  2.0,  8.0, 2.0, 1.0, 1.0, 1.0 } }
	{ {  1.0, 1.0, 1.0, 1.0, 1.0, 2.0,  4.0,  2.0, 1.0, 1.0, 1.0, 1.0, 1.0 },
	  {  1.0, 1.0, 1.0, 1.0, 1.0, 3.0,  3.0, 1.0,  1.0, 1.0, 1.0, 1.0, 1.0 } },
};
double *filtw[2][2] = { { &filtw_v[0][0][FWIDTH], &filtw_v[0][1][FWIDTH] },
                        { &filtw_v[1][0][FWIDTH], &filtw_v[1][1][FWIDTH] } };

#define MX 2.0
#define MN -0.5 

// Compute the filter shapes
// vv = 0 for horizontal, 1 for vertical
static void compute(int vv) {
	int ph, ii, i, jj, j;
	double iv, ov; 
	int fcount[2];
	int niv = sizeof(in[vv])/sizeof(double);
	int nov = sizeof(out[vv])/sizeof(double);

	// Clear the filters
	for (ph = 0; ph < 2; ph++) {
		for (j = -FWIDTH; j <= FWIDTH; j++) {
			filt[vv][ph][j] = 0.0;
			filtx[vv][ph][j] = MX;
			filtn[vv][ph][j] = MN;
		}
		fcount[ph] = 0;
	}

	// Discover an input value
	for (ii = 0; ii < niv; ii++) {
		double rgb[3], ycc[3];
		double prop, propx, propn;

		if (in[ii] == 0)
			continue;

		iv = in[vv][ii]; 
		rgb[0] = rgb[1] = rgb[2] = iv;
		ccast2YCbCr(NULL, ycc, rgb);
		iv = ycc[0];

		ph = ii & 1;
		jj = (int)floor(ii * 1.5 + 0.5);

		for (j = -FWIDTH; j <= FWIDTH; j++) {
			int k = jj + j;
			if (k < 0 || k >= nov)
				continue;
			ov = out[vv][k];

			prop = ((ov - 16.0)/219.0)/((iv - 16.0)/219.0);
			propx = ((ov - 16.0)/219.0)/((iv - 0.5 - 16.0)/219.0);
			propn = ((ov - 16.0)/219.0)/((iv + 0.5 - 16.0)/219.0);

			if (propx < propn) {
				double tt = propn;
				propn = propx;
				propx = tt;
			}

//printf("~1 phase %d, off %d, iv %f ov %f, prop %f\n",ph,j,iv,ov,prop);
			filt[vv][ph][j] += prop;
			if (propx < filtx[vv][ph][j])
				filtx[vv][ph][j] = propx;
			if (propn > filtn[vv][ph][j])
				filtn[vv][ph][j] = propn;
		}
		fcount[ph]++;
	}

	// Compute average values
	for (ph = 0; ph < 2; ph++) {
		for (j = -FWIDTH; j <= FWIDTH; j++)
			filt[vv][ph][j] /= (double)fcount[ph];
	}
}

#define FCO2IX(bank, off) (bank ? fpos[vv][0] - fneg[vv][0] + 1 + off - fneg[vv][1] : off - fneg[vv][0])

// Compute the filter shapes using SVD
// vv = 0 for horizontal, 1 for vertical
static void compute2(int vv) {
	int niv = sizeof(in[vv])/sizeof(double);		/* Number of input values */
	int nov = sizeof(out[vv])/sizeof(double);		/* Number of output values */
	int novextra = 0;
	int nfc = fpos[vv][0] - fneg[vv][0] + 1	
	        + fpos[vv][1] - fneg[vv][1] + 1;		/* Number of filter coeficients */
	double **A, *b;
	int oe;					/* Even or odd */
	int j, i;

#ifdef SUM_CONSTRAINT
	novextra = 3;
#endif

	nov += novextra;			/* Extra constraint of sum */

	/* We assume nov > nfc */
	A = dmatrixz(0, nov-1, 0, nfc-1);
	b = dvectorz(0, nov-1);

	/* For each output value */
	for (j = 0; j < (nov-novextra); j++) {
		double ww = 1.0;

#ifdef DO_WEIGHTING
		/* Figure out the weighting */
		for (oe = 0; oe < 2; oe++) {
			int fix;			/* Filter index */

			/* For offset range of filter */
			for (fix = fneg[vv][oe]; fix <= fpos[vv][oe]; fix++) {
				int ocx, icx, ph;

				ocx = j - fix;					/* Output center of filter */
				if (ocx < 0 || ocx >= nov)
					continue;					/* Filter would never get applied */
				if (((2 * ocx) % 3) == 2)
					continue;					/* Would never get applied */
				icx = (int)floor(ocx / 1.5);	/* Input center index for this output */
				if (icx < 0 || icx >= niv)
					continue;					/* Filter would never get applied */
				ph = icx & 1;					/* Phase of filter */
				if (ph != oe)					/* Not a filter that would appear at this ouput */
					continue;
				if (in[vv][icx] >= 200.0)
					ww = filtw[vv][ph][fix];
			}
		}
#endif /* DO_WEIGHTING */

		/* For even and odd filters */
		for (oe = 0; oe < 2; oe++) {
			int fix;			/* Filter index */

			/* For offset range of filter */
			for (fix = fneg[vv][oe]; fix <= fpos[vv][oe]; fix++) {
				double rgb[3], ycc[3];
				int ocx, icx, ph;

				ocx = j - fix;					/* Output center of filter */
				if (ocx < 0 || ocx >= nov)
					continue;					/* Filter would never get applied */
				if (((2 * ocx) % 3) == 2)
					continue;					/* Would never get applied */
				icx = (int)floor(ocx / 1.5);	/* Input center index for this output */
				if (icx < 0 || icx >= niv)
					continue;					/* Filter would never get applied */
				ph = icx & 1;					/* Phase of filter */
				if (ph != oe)					/* Not a filter that would appear at this ouput */
					continue;
//printf("j = %d/%d, k = %d/%d, ix = %d/%d\n",j,nov,oe * NWIDTH + FWIDTH + fix,nfc,ix,niv);
				rgb[0] = rgb[1] = rgb[2] = in[vv][icx];
				ccast2YCbCr(NULL, ycc, rgb);
				A[j][FCO2IX(oe, fix)] += ww * ycc[0];
//printf("A[%d][%d] = %f\n",j,FCO2IX(oe, fix),A[j][FCO2IX(oe, fix)]);
			}
		}
		b[j] = ww * out[vv][j];
//printf("b[%d] = %f\n",j,b[j]);
	}

#ifdef SUM_CONSTRAINT
	/* Add sum constraints */
	/* For 3 repeating output slots */
	for (j = nov-novextra; j < nov; j++) {
		double ww = 10000.0;
		int jj = j - (nov-novextra);

		b[j] = ww;

		/* For even and odd filters */
		for (oe = 0; oe < 2; oe++) {
			int fix;			/* Filter index */

			/* For offset range of filter */
			for (fix = fneg[vv][oe]; fix <= fpos[vv][oe]; fix++) {
				double rgb[3], ycc[3];
				int ocx, ocx2, icx, ph;

				ocx = j - fix;					/* Output center of filter */
				ocx2 = 2 * ocx;
				
				while (ocx2 < 0)
					ocx2 += 3;
				while (ocx2 >= 3)
					ocx2 -= 3;

				if (ocx2 == 2)
					continue;					/* Would never get applied */

				while (ocx < 0)
					ocx += 3;
				while (ocx >= 3)
					ocx -= 3;

				icx = (int)floor(ocx / 1.5);	/* Input center index for this output */
				ph = icx & 1;					/* Phase of filter */
				if (ph != oe)					/* Not a filter that would appear at this ouput */
					continue;
				A[j][FCO2IX(oe, fix)] = ww;
printf("A[%d][%d] = %f\n",j,FCO2IX(oe, fix),A[j][FCO2IX(oe, fix)]);
			}
		}
	}
#endif /* SUM_CONSTRAINT */

	/* Solve the equation A.x = b using SVD */
	/* (The w[] values are thresholded for best accuracy) */
	/* Return non-zero if no solution found */
	if (svdsolve(A, b, nov, nfc))
		error("svdsolve failed");

	/* Print the filter shape */
	/* and copy to the filter */
	printf("SVD computed for %s:\n", vv ? "vertical" : "horizontal");
	for (oe = 0; oe < 2; oe++) {
		int fix;			/* Filter index */
		double sum = 0.0;
	
		printf("Phase %d\n",oe);
//		for (fix = -FWIDTH; fix <= FWIDTH; fix++) {
		for (fix = fneg[vv][oe]; fix <= fpos[vv][oe]; fix++) {
			printf(" %d -> %f\n",fix, b[FCO2IX(oe, fix)]);
			sum += b[FCO2IX(oe, fix)];
			filt[vv][oe][fix] = b[FCO2IX(oe, fix)];
		}
		printf("sum = %f\n",sum);
	}
}

void check(int vv) {
	double *chout;
	int niv = sizeof(in[vv])/sizeof(double);			/* Number of input values */
	int nov = sizeof(out[vv])/sizeof(double);		/* Number of output values */

	int range, i, ii, j;
	double xc, x, iv, tw, w, y;
	double cout, terr = 0.0;

printf("~1 nov = %d\n",nov);
	if ((chout = (double *)malloc(sizeof(double) * nov)) == NULL)
		error("Malloc failed");

	// Clear the output
	for (i = 0; i < nov; i++)
		chout[i] = 0.0;

	// For  all the input value
	for (ii = 0; ii < niv; ii++) {
		int ph, jj;
		double rgb[3], ycc[3];
		double prop;

		iv = in[vv][ii]; 
		rgb[0] = rgb[1] = rgb[2] = iv;
		ccast2YCbCr(NULL, ycc, rgb);
		iv = ycc[0];

		ph = ii & 1;
		jj = (int)floor(ii * 1.5 + 0.5);

		for (j = -FWIDTH; j <= FWIDTH; j++) {
			int k = jj + j;
			if (k < 0 || k >= nov)
				continue;

//if ((jj + j) == 4) printf("[%d] += w %f * iv %f\n",jj + j,  filt[ph][j], iv);
			chout[k] += filt[vv][ph][j] * iv;
		}
	}

	for (i = 0; i < nov; i++) {
		double ov, ee;
		ov = chout[i];
		ov = floor(ov + 0.5);
#ifdef NEVER
		if (ov < 0.0)
			ov = 0.0;
		else if (ov > 255.0)
			ov = 255.0;
#endif
		ee = ov - out[vv][i]; 
		terr += ee * ee;
		printf("out %d = %f should be %f err %f\n",i,chout[i],out[vv][i],ee);
	}

	printf("Total err = %f RMS\n",sqrt(terr));
}

int
main(int argc,
	char *argv[]
) {
	int ph, vv, j;

	double err;
	double cp[2];			/* Initial starting point */
	double s[2];			/* Size of initial search area */

	printf("Hi there\n");

#ifdef NEVER
	compute(1);

	// Print filter shape
	printf("Directly computed:\n");
	for (ph = 0; ph < 2; ph++) {
		printf("Phase %d\n",ph);
		for (j = -FWIDTH; j <= FWIDTH; j++)
			printf(" %d -> min %f, avg %f, max %f\n",j,filtn[1][ph][j],filt[1][ph][j],filtx[1][ph][j]);
	}
#endif

	for (vv = 0; vv < 2; vv++) {
		compute2(vv);
		check(vv);
	}

	/* Output code to stdout */
	for (ph = 0; ph < 2; ph++) {
		fprintf(stderr,"/* Weightings [horiz/vert] */\n");
		fprintf(stderr,"double filt_v_%s[2][%s_WIDTH] = {\n",ph ? "od" : "ev", ph ? "OD" : "EV");

		for (vv = 0; vv < 2; vv++) {
			int fix;			/* Filter index */
		
			fprintf(stderr,"{ ");
			for (fix = fneg[vv][ph]; fix <= fpos[vv][ph]; fix++) {
				if (fix > fneg[vv][ph])
				fprintf(stderr,", ");
				fprintf(stderr,"%f", filt[vv][ph][fix]);
			}
			fprintf(stderr," }%s\n",vv == 0 ? "," : "");
		}
		fprintf(stderr,"};\n\n");
	}

	return 0;
}

