/*******************************************************************
                          resize module
 *******************************************************************/
#define RESIZE_C
#include "resize.h"

#include <math.h>
#include <stdlib.h>
#include <float.h>

#ifndef PI
#define PI (atan(1)*4)
#endif

/* grobal */
FRAME *resize(FRAME *in, RESIZE_PARAMETER *prm);
RESIZE_PARAMETER *create_resize_parameter(SEQUENCE_HEADER *seq, M2V_CONFIG *cfg);
void release_resize_parameter(RESIZE_PARAMETER *prm);

/* local */
static void setup_interpolation_parameter(int source_length, int result_length, RESIZE_PARAMETER *out);
static void setup_decimation_parameter(int source_length, int result_length, RESIZE_PARAMETER *out);
static double lanczos3_weight(double phase);

static void setup_crop_parameter(int source_length, RESIZE_PARAMETER *r);

static void component_resize(unsigned char *in, unsigned char *out, RESIZE_PARAMETER *prm);

/*-----------------------------------------------------------------*/
FRAME *resize(FRAME *in, RESIZE_PARAMETER *prm)
{
	FRAME *r;

	if(prm == NULL){
		return in;
	}

	r = new_frame(prm->out_step, prm->height);
	if(r == NULL){
		return NULL;
	}

	component_resize(in->y, r->y, prm);
	component_resize(in->u, r->u, prm);
	component_resize(in->v, r->v, prm);

	delete_frame(in);

	return r;
}

/*-----------------------------------------------------------------*/
RESIZE_PARAMETER *create_resize_parameter(SEQUENCE_HEADER *seq, M2V_CONFIG *cfg)
{
	RESIZE_PARAMETER *r;

	int n;
	int src_width;

	if(cfg->aspect_ratio == M2V_CONFIG_IGNORE_ASPECT_RATIO){
		return NULL;
	}

	r = (RESIZE_PARAMETER *)malloc(sizeof(RESIZE_PARAMETER));
	if(r == NULL){
		return NULL;
	}

	if(seq->has_sequence_display_extension){
		if(    (seq->sd.display_h_size == 0)
		    || (seq->sd.display_v_size == 0)
		    || (seq->sd.display_h_size > seq->h_size)
		    || (seq->sd.display_v_size > seq->v_size) ) {
			seq->sd.display_h_size = seq->h_size;
			seq->sd.display_v_size = seq->v_size;
		}
	}
	
	if(seq->has_sequence_display_extension){
		r->height = seq->sd.display_v_size;
	}else{
		r->height = seq->orig_v_size;
	}

	if(seq->has_sequence_display_extension){
		src_width = seq->sd.display_h_size;
	}else{
		src_width = seq->orig_h_size;
	}
	
	switch(seq->aspect_ratio){
	case 2: /* 4:3 */
		r->width = r->height * 4 / 3;
		break;
	case 3: /* 16:9 */
		r->width = r->height * 16 / 9;
		break;
	case 4:
		r->width = r->height * 221 / 100;
		break;
	default:
		r->width = src_width;
	}

	if(r->width == seq->orig_h_size){
		free(r);
		return NULL;
	}

	r->in_step = seq->h_size;
	
	if(seq->has_sequence_display_extension){
		n = (seq->orig_v_size - seq->sd.display_v_size) / 2;
		r->in_offset = r->in_step * n;

		n = (seq->orig_h_size - seq->sd.display_h_size) / 2;
		r->in_offset += n;
	}else{
		r->in_offset = 0;
	}

	r->out_step = (r->width + 15) >> 4;
	r->out_step <<= 4;

	if(r->width < src_width){
		setup_decimation_parameter(src_width, r->width, r);
	}else if(r->width == src_width){
		setup_crop_parameter(src_width, r);
	}else{
		setup_interpolation_parameter(src_width, r->width, r);
	}

	return r;
}

/*-----------------------------------------------------------------*/
void release_resize_parameter(RESIZE_PARAMETER *prm)
{
	int i;
	
	if(prm == NULL){
		return;
	}

	for(i=0;i<prm->length;i++){
		free(prm->weight[i]);
		free(prm->index[i]);
	}

	free(prm->index);
	free(prm->weight);

	free(prm);
}

/*-----------------------------------------------------------------*/
static void setup_interpolation_parameter(int source_length, int result_length, RESIZE_PARAMETER *out)
{
	int i,j,n;
	double *work;
	double  sum;
	double  pos;

	out->length = result_length;
	out->index = (int **)malloc(sizeof(int *)*out->length);
	out->weight = (int **)malloc(sizeof(int *)*out->length);
	out->tap = 6;

	for(i=0;i<result_length;i++){
		out->weight[i] = (int *)malloc(sizeof(int)*out->tap);
		out->index[i] = (int *)malloc(sizeof(int)*out->tap);
	}
	work = (double *)malloc(sizeof(double)*out->tap);

	__asm {emms};

	for(i=0;i<result_length;i++){
		pos = (i+0.5)*source_length;
		pos /= result_length;
		n = (int) pos;
		pos = n - pos;
		n -= (out->tap / 2);
		pos -= (out->tap / 2);
		sum = 0;
		for(j=0;j<out->tap;j++){
			if(n < 0){
				out->index[i][j] = 0;
			}else if(n >= source_length){
				out->index[i][j] = source_length-1;
			}else{
				out->index[i][j] = n;
			}
			work[j] = lanczos3_weight(pos);
			sum += work[j];
			pos += 1;
			n += 1;
		}

		for(j=0;j<out->tap;j++){
			out->weight[i][j] = (int)((work[j] / sum) * (1<<16));
		}
	}

	free(work);
}

/*-----------------------------------------------------------------*/
static void setup_decimation_parameter(int source_length, int result_length, RESIZE_PARAMETER *out)
{
	int i,j,n;
	double *work;
	double  sum;
	double  pos, phase;

	out->length = result_length;
	out->weight = (int **)malloc(sizeof(int *)*out->length);
	out->index = (int **)malloc(sizeof(int *)*out->length);
	
	out->tap = 6 * source_length / result_length;
	if((source_length % result_length) == 0){
		out->tap -= 1;
	}

	for(i=0;i<result_length;i++){
		out->weight[i] = (int *)malloc(sizeof(int)*out->tap);
		out->index[i] = (int *)malloc(sizeof(int)*out->tap);
	}
	work = (double *)malloc(sizeof(double)*out->tap);

	for(i=0;i<result_length;i++){
		pos = (i+0.5)*source_length;
		pos /= result_length;
		n = (int) pos;
		n -= (out->tap / 2);
		sum = 0;
		for(j=0;j<out->tap;j++){
			phase = (n+0.5)*result_length;
			phase /= source_length;
			phase -= i;
			if(n < 0){
				out->index[i][j] = 0;
			}else if(n >= source_length){
				out->index[i][j] = source_length-1;
			}else{
				out->index[i][j] = n;
			}
			work[j] = lanczos3_weight(phase);
			sum += work[j];
			n += 1;
		}

		for(j=0;j<out->tap;j++){
			out->weight[i][j] = (int)((work[j] / sum) * (1<<16));
		}
	}

	free(work);
}

/*-----------------------------------------------------------------*/
static double lanczos3_weight(double phase)
{
	double ret;
	
	if(fabs(phase) < DBL_EPSILON){
		return 1.0;
	}

	if(fabs(phase) >= 3.0){
		return 0.0;
	}

	ret = sin(PI*phase)*sin(PI*phase/3)/(PI*PI*phase*phase/3);

	return ret;
}

/*-----------------------------------------------------------------*/
static void setup_crop_parameter(int result_length, RESIZE_PARAMETER *out)
{
	int i;

	out->length = result_length;
	out->index = (int **)malloc(sizeof(int)*out->length);
	out->weight = (int **)malloc(sizeof(int *)*out->length);
	out->tap = 1;

	for(i=0;i<result_length;i++){
		out->weight[i] = (int *)malloc(sizeof(int));
		out->index[i] = (int *)malloc(sizeof(int));
	}

	for(i=0;i<result_length;i++){
		out->index[i][0] = i;
		out->weight[i][0] = 1<<16;
	}

	return;
}

/*-----------------------------------------------------------------*/
static void component_resize(unsigned char *in, unsigned char *out, RESIZE_PARAMETER *prm)
{
	int x,y;
	int i;
	int w;

	in += prm->in_offset;

	for(y=0;y<prm->height;y++){
		for(x=0;x<prm->width;x++){
			w = 0;
			for(i=0;i<prm->tap;i++){
				w += in[prm->index[x][i]] * prm->weight[x][i];
			}
			w += 32768;
			out[x] = uchar_clip_table[UCHAR_CLIP_TABLE_OFFSET+(w>>16)];
		}
		out += prm->out_step;
		in += prm->in_step;
	}
}

