/* ****************************************************************
 *                         clr_sample.c
 * ****************************************************************
 *  MODULE PURPOSE:
 *      This module contains the routines for spectral sampling.
 *      The operations include defining the sampling space,
 *      sampling spectral curves, and transformations from spectral
 *      sample space to RGB or XYZ.
 *
 *  MODULE CONTENTS:
 *      CLR_init_samples    - initialize spectral sampling
 *      CLR_num_samples     - returns the number of samples
 *      CLR_spect_to_sample - sample a spectral curve
 *      CLR_get_sample_rgb  - get the sample to RGB matrix
 *      CLR_get_sample_xyz  - get the sample to XYZ matrix
 *      CLR_reconstruct     - reconstruct a spectral curve
 *      CLR_exit_samples    - finish with spectral sampling
 *
 *  NOTES:
 *      > The CLR_ routines must be initialized (CLR_init()) before
 *          using any of the sampling routines.
 *
 *      > When the CLR_SAMPLE_HALL method is selected, sampling
 *          uses abutting, non-overlapping, box sample and
 *          reconstruction functions are described in Section
 *          3.5.2 Spectral Sampling Approaches, and in Hall (1983).
 *          This provides continuous sampling between the low bound
 *          of the first sample and the high bound of the last
 *          sample.  For applications requiring discrete isolated
 *          samples, user modification of these routines
 *          is required.
 *
 *      > When the CLR_SAMPLE_MEYER method is used, 4 samples are
 *          used as described in Meyer (1988).
 */
#include <stdio.h>
#include "clr.h"

static int      sample_type = -1;
static int      samples = 0;
static int      *bounds = NULL;
static double   XYZ_to_ACC[3][3] = {{-0.0177,   1.0090, 0.0073},
				    {-1.5370,   1.0821, 0.3209},
				    { 0.1946,  -0.2045, 0.5264}};
static double   ACC_to_XYZ[3][3];
static double   samp_to_ACC[3][4] =
		    {{0.00000,  0.18892,    0.67493,    0.19253},
		     {0.00000,  0.00000,    0.31824,   -0.46008},
		     {0.54640,  0.00000,    0.00000,    0.00000}};

extern char     *malloc();

/* ****************************************************************
 * CLR_init_samples (method, num_samples, sample_bounds)
 *  int     method          (in) - sampling method:
 *                              CLR_SAMPLE_MEYER    Meyer (1988)
 *                              CLR_SAMPLE_HALL     Hall (1983)
 *  int     num_samples     (in) - number of sample functions
 *  int     sample_bounds[] (in) - boundaries of the sampling
 *                              functions.  There must be
 *                              num_samples+1 bounds arranged in
 *                              ascending order in this array.
 *
 * For the CLR_SAMPLE_HALL method the bound wavelength is included
 *  in the sampling function.  For example, using 3 samples with
 *  bounds at (411, 491, 571, 651), the actual samples are 411-490,
 *  491-570, and 571-650.
 *
 * The CLR_SAMPLE_MEYER method uses a prescribed sampling with 4
 *  samples.  The num_samples and sample_bounds arguments are
 *  ignored.
 *
 * Returns TRUE if successful, FALSE if sample bounds are not valid
 *  or sampling is previously initialized to some other value.
 */
CLR_init_samples (method, num_samples, sample_bounds)
int     method;
int     num_samples;
int     *sample_bounds;
{   int     ct;

    if (method == CLR_SAMPLE_MEYER) {
	samples = 4;
	sample_type = method;
	CLR_t_inverse (XYZ_to_ACC, ACC_to_XYZ);
    }
    else if (method == CLR_SAMPLE_HALL) {
	/* There are two ways to do this, one is to save a
	 *  complete sampling curve for each sample.  The
	 *  other is to do the computation on the fly.  Here
	 *  it's done on the fly.  Only the bounds are saved.
	 */
	if (num_samples <= 0) return FALSE;
	if ((bounds = (int *)malloc((unsigned)(sizeof(int) *
	    (num_samples + 1)))) == NULL) goto error;

	bounds[0] = sample_bounds[0];
	for (ct=0; ct<num_samples; ct++) {
	    if (sample_bounds[ct+1] <= sample_bounds[ct])
		goto error;
	    bounds[ct+1] = sample_bounds[ct+1];
	}

	samples = num_samples;
	sample_type = method;
    }
    else {
	goto error;
    }
    return TRUE;

error:
    samples = 0;
    if (bounds != NULL) free((char *)bounds);
    return FALSE;
}

/* ****************************************************************
 * CLR_num_samples()
 *
 * Returns the number of samples for which sampling is initialized.
 *  Returns 0 if sampling is not initialized.
 */
CLR_num_samples()
{   return samples;
}


/* ****************************************************************
 * CLR_spect_to_sample (spectral, sample)
 *  double  *spectral   (in)  - spectral curve to be sampled
 *  double  *sample     (mod) - array to receive the sampled
 *                          values.
 * Samples 'spectral' and loads the sample values into 'sample'.
 *  Returns TRUE if successful and FALSE if CLR_ or the sampling
 *  has not been initialized
 */
CLR_spect_to_sample (spectral, sample)
double      *spectral;
double      *sample;
{   int     cur_wl, ct, max_wl, cur_samp;
    double  cur_sum;

    if (samples <= 0) return FALSE;
    if ((cur_wl = CLR_get_min_wl()) < 0) return FALSE;
    max_wl = CLR_get_max_wl();

    if (sample_type == CLR_SAMPLE_MEYER) {
	/* sample at 456.4nm, 490.9nm, 557.7nm, and 631.4nm
	 */
	if ((cur_wl > 456) || (max_wl < 457)) *sample++ = 0.0;
	else *sample++ = spectral[456-cur_wl] + (0.4 *
		(spectral[457-cur_wl] - spectral[456-cur_wl]));

	if ((cur_wl > 490) || (max_wl < 491)) *sample++ = 0.0;
	else *sample++ = spectral[490-cur_wl] + (0.9 *
		(spectral[491-cur_wl] - spectral[490-cur_wl]));

	if ((cur_wl > 557) || (max_wl < 558)) *sample++ = 0.0;
	else *sample++ = spectral[557-cur_wl] + (0.7 *
		(spectral[558-cur_wl] - spectral[557-cur_wl]));

	if ((cur_wl > 631) || (max_wl < 632)) *sample++ = 0.0;
	else *sample++ = spectral[631-cur_wl] + (0.4 *
		(spectral[632-cur_wl] - spectral[631-cur_wl]));
    }
    else {
	for ( ;cur_wl<bounds[0]; cur_wl++, spectral++) ;

	for (cur_samp=1, cur_sum=0.0, ct=0; cur_wl<=max_wl;
		cur_wl++) {
	    while (cur_wl>=bounds[cur_samp]) {
		if (ct == 0) *sample++ = 0.0;
		else *sample++ = cur_sum / ct;
		if (++cur_samp > samples) return TRUE;
		cur_sum = 0.0;
		ct = 0;
	    }
	    cur_sum += *spectral++;
	    ct++;
	}
	*sample = cur_sum / ct;
    }
    return TRUE;
}

/* ****************************************************************
 * CLR_get_sample_rgb (matrix)
 *  double  *matrix     (mod) - matrix to be filled.
 *
 * Returns the matrix for conversion from the sampled space to the
 *  RGB the CLR_ routines have been initialized for.  The matrix
 *  is a 3 x num_samples matrix.
 */
CLR_get_sample_rgb(matrix)
double      *matrix;
{
    double      *xyz = NULL;
    double      xyz_rgb[3][3];
    int         ct;

    /* get the XYZ matrix, then transform it into RGB
     */
    if ((xyz = (double *)malloc((unsigned)(3 *
	sizeof(double) * samples))) == NULL) goto error;
    if (!CLR_get_sample_xyz(xyz)) goto error;
    if (!CLR_get_xyz_rgb(xyz_rgb)) goto error;

    for (ct=0; ct<samples; ct++, matrix++, xyz++) {
	matrix[0] = (xyz_rgb[0][0] * xyz[0]) +
		    (xyz_rgb[0][1] * xyz[samples]) +
		    (xyz_rgb[0][2] * xyz[2*samples]);
	matrix[samples] = (xyz_rgb[1][0] * xyz[0]) +
		    (xyz_rgb[1][1] * xyz[samples]) +
		    (xyz_rgb[1][2] * xyz[2*samples]);
	matrix[2*samples] = (xyz_rgb[2][0] * xyz[0]) +
		    (xyz_rgb[2][1] * xyz[samples]) +
		    (xyz_rgb[2][2] * xyz[2*samples]);
    }

    free((char *)xyz);
    return TRUE;

error:
    if (xyz != NULL) free((char *)xyz);
    return FALSE;
}

/* ****************************************************************
 * CLR_get_sample_xyz (matrix)
 *  double  *matrix     (mod) - matrix to be filled.
 *
 * Returns the matrix for conversion from the sampled space to
 *  CIEXYZ. The matrix is a 3 x num_samples matrix.
 */
CLR_get_sample_xyz(matrix)
double      *matrix;
{
    int     min_wl, max_wl, cur_wl, cur_samp;
    double  *reconst, *cur_r, fill, *samp_mat;
    CLR_XYZ xyz;

    if (samples <= 0) return FALSE;
    if (sample_type == CLR_SAMPLE_MEYER) {
	/* concatenate the sample to ACC matrix with the ACC_to_XYZ
	 *  matrix.  The divide by 1.057863 is a normalization so
	 *  than an identity curve has a Y value of 1.0 following
	 *  the conventions used in the CLR_routines.
	 */
	samp_mat = samp_to_ACC[0];
	for (cur_samp=0; cur_samp<samples; cur_samp++,
		matrix++, samp_mat++) {
	    matrix[0] = ((ACC_to_XYZ[0][0] * samp_mat[0]) +
		(ACC_to_XYZ[0][1] * samp_mat[samples]) +
		(ACC_to_XYZ[0][2] * samp_mat[2*samples])) /
		1.057863;
	    matrix[samples] = ((ACC_to_XYZ[1][0] * samp_mat[0]) +
		(ACC_to_XYZ[1][1] * samp_mat[samples]) +
		(ACC_to_XYZ[1][2] * samp_mat[2*samples])) /
		1.057863;
	    matrix[2*samples] = ((ACC_to_XYZ[2][0] * samp_mat[0]) +
		(ACC_to_XYZ[2][1] * samp_mat[samples]) +
		(ACC_to_XYZ[2][2] * samp_mat[2*samples])) /
		1.057863;
	}
    }
    else {
	min_wl = CLR_get_min_wl();
	max_wl = CLR_get_max_wl();
	/* allocate space for the reconstruction function
	 */
	if ((reconst = (double *)malloc(
	    (unsigned)(sizeof(double) *
	    (max_wl - min_wl + 1)))) == NULL) goto error;

	/* Build each reconstruction function and sample it into xyz.
	 *  Load the values into the matrix
	 */
	for (cur_samp=0; cur_samp<samples; cur_samp++, matrix++) {
	    cur_r = reconst;

	    if (cur_samp == 0) fill = 1.0;
	    else fill = 0.0;

	    for (cur_wl=min_wl ;(cur_wl<bounds[cur_samp]) &&
		(cur_wl<max_wl); cur_wl++) *cur_r++ = fill;
	    for ( ;(cur_wl<bounds[cur_samp+1]) &&
		(cur_wl<=max_wl); cur_wl++) *cur_r++ = 1.0;
	    if (cur_samp == (samples-1)) fill = 1.0;
	    else fill = 0.0;
	    for ( ;cur_wl<=max_wl; cur_wl++) *cur_r++ = fill;
	    xyz = CLR_spect_to_xyz (reconst);
	    matrix[0] = xyz.x;
	    matrix[samples] = xyz.y;
	    matrix[2*samples] = xyz.z;
	}
    }

    free((char *)reconst);
    return TRUE;

error:
    if (reconst != NULL) free((char *)reconst);
    return FALSE;
}

/* ****************************************************************
 * CLR_reconstruct (sample, spectral)
 *  double  *sample     (in)  - the sample values
 *  double  *spectral   (mod) - the reconstructed spectral curve
 *
 * Reconstructs a spectral curve from the sample values.  The
 *  reconstruction functions are box functions resulting a a step
 *  function as the reconstructed curve.
 */
CLR_reconstruct (sample, spectral)
double      *sample, *spectral;
{   int     cur_wl, max_wl, cur_sample;
    if (samples <= 0) return FALSE;
    if (sample_type == CLR_SAMPLE_MEYER) {
	return FALSE;
    }
    else if (sample_type == CLR_SAMPLE_HALL) {
	cur_wl = CLR_get_min_wl();
	max_wl = CLR_get_max_wl();
	cur_sample = 1;

	while (cur_wl<=max_wl) {
	    if (cur_wl < bounds[cur_sample])
		*spectral++ = *sample;
	    else if (cur_sample >= samples)
		*spectral++ = *sample;
	    else {
		cur_sample++;
		sample++;
		*spectral++ = *sample;
	    }
	    cur_wl++;
	}
    }
    return TRUE;
}

/* ****************************************************************
 * CLR_exit_samples()
 *
 * Complete use of spectral sampling, free any allocated space.
 */
CLR_exit_samples ()
{   sample_type = -1;
    samples = 0;
    if (bounds != NULL) {
	free((char *)bounds);
	bounds = NULL;
    }
    return TRUE;
}
/* ************************************************************* */
