/*
test_pat_I
This frei0r plugin generates test patterns for measurement of
spatial impulse and step responses

Version 0.1	may 2010

Copyright (C) 2010  Marko Cebokli    http://lea.hamradio.si/~s57uuu


 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

/***********************************************************
Test patterns: spatial impulse and step response

The patterns are drawn into a temporary float array, for two reasons:
1. drawing routines are color model independent,
2. drawing is done only when a parameter changes.

only the function float2color()
needs to care about color models, endianness, DV legality etc.

*************************************************************/

//compile:	gcc -Wall -c -fPIC test_pat_I.c -o test_pat_I.o

//link: gcc -lm -shared -o test_pat_I.so test_pat_I.o

#include <stdlib.h>
#include <assert.h>
#include <math.h>

#include "frei0r.h"



double PI=3.14159265358979;

typedef struct
	{
	float r;
	float g;
	float b;
	float a;
	} float_rgba;


//----------------------------------------------------------
void draw_rectangle(float *sl, int w, int h, int x, int y, int wr, int hr, float gray)
{
int i,j;
int zx,kx,zy,ky;

zx=x;  if (zx<0) zx=0;
zy=y;  if (zy<0) zy=0;
kx=x+wr;  if (kx>w) kx=w;
ky=y+hr;  if (ky>h) ky=h;
for (i=zy;i<ky;i++)
	for (j=zx;j<kx;j++)
		sl[w*i+j]=gray;

}

//----------------------------------------------------
//pravokotna pika
void pika_p(float *sl, int w, int h, float size, float amp)
{
int i;

for (i=0;i<w*h;i++) sl[i]=0.5-amp/2.0;	//background
draw_rectangle(sl, w, h, w/2-size/2, h/2-size/2, size, size, 0.5+amp/2.0);

}

//----------------------------------------------------
//okrogla pika  (raised cos)
void pika_o(float *sl, int w, int h, float size, float amp)
{
int i,j;
float x,y,r,g;

for (i=0;i<w*h;i++) sl[i]=0.5-amp/2.0;	//background

for (i=0;i<size;i++)
	for (j=0;j<size;j++)
		{
		x=(float)j-size/2.0+0.5;
		y=(float)i-size/2.0+0.5;
		r=sqrtf(x*x+y*y);
		if (r>size/2.0) r=size/2.0;
		g=0.5+amp/2.0*cosf(r/size*2.0*PI);
		sl[(i+h/2-(int)size/2)*w+j+w/2-(int)size/2]=g;
		}

}

//----------------------------------------------------
//crta pravokotna
void crta_p(float *sl, int w, int h, float size, float amp, float tilt)
{
int i,j;
float d,st,ct;

st=sinf(tilt);
ct=cosf(tilt);
for (i=0;i<h;i++)
	for (j=0;j<w;j++)
		{
		d=(i-h/2)*ct+(j-w/2)*st;
		if (fabsf(d)>size/2.0)
			{
			sl[i*w+j]=0.5-amp/2.0;
			}
		else
			{
			sl[i*w+j]=0.5+amp/2.0;
			}
		}

}

//----------------------------------------------------
//crta   raised cos
void crta(float *sl, int w, int h, float size, float amp, float tilt)
{
int i,j;
float d,st,ct,g;

if (size==0.0) return;
st=sinf(tilt);
ct=cosf(tilt);
for (i=0;i<h;i++)
	for (j=0;j<w;j++)
		{
		d=(i-h/2)*ct+(j-w/2)*st;
		if (fabsf(d)>size/2.0)
			{
			sl[i*w+j]=0.5-amp/2.0;
			}
		else
			{
			if (d>size/2.0) d=size/2.0;
			g=0.5+amp/2.0*cosf(d/size*2.0*PI);
			sl[i*w+j]=g;
			}
		}

}

//----------------------------------------------------
//crta step  raised cos, oz. pravokotna, ce das size=1
void crta_s(float *sl, int w, int h, float size, float amp, float tilt)
{
int i,j;
float d,st,ct,g;

if (size==0.0) return;
st=sinf(tilt);
ct=cosf(tilt);
for (i=0;i<h;i++)
	for (j=0;j<w;j++)
		{
		d=(i-h/2)*ct+(j-w/2)*st;
		if (fabsf(d)>size/2.0)
			{
			if (d>0.0)
				sl[i*w+j]=0.5-amp/2.0;
			else
				sl[i*w+j]=0.5+amp/2.0;
			}
		else
			{
			if (d>size/2.0) d=size/2.0;
			g=0.5-amp/2.0*sinf(d/size*PI);
			sl[i*w+j]=g;
			}
		}

}

//----------------------------------------------------
//crta step  linear ramp, oz. pravokotna, ce das size=1
void crta_r(float *sl, int w, int h, float size, float amp, float tilt)
{
int i,j;
float d,st,ct,g;

if (size==0.0) return;
st=sinf(tilt);
ct=cosf(tilt);
for (i=0;i<h;i++)
	for (j=0;j<w;j++)
		{
		d=(i-h/2)*ct+(j-w/2)*st;
		if (fabsf(d)>size/2.0)
			{
			if (d>0.0)
				sl[i*w+j]=0.5-amp/2.0;
			else
				sl[i*w+j]=0.5+amp/2.0;
			}
		else
			{
			if (d>size/2.0) d=size/2.0;
			g = 0.5-amp*(d/size);
			sl[i*w+j]=g;
			}
		}

}

//-----------------------------------------------------
//converts the internal monochrome float image into
//Frei0r rgba8888 color
//ch selects the channel   0=all  1=R  2=G  3=B
//sets alpha to opaque
void float2color(float *sl, uint32_t* outframe, int w , int h, int ch)
{
int i,ri,gi,bi;
uint32_t p;
float r,g,b;

switch (ch)
	{
	case 0:		//all (gray)
		for (i=0;i<w*h;i++)
			{
			p=(uint32_t)(255.0*sl[i]) & 0xFF;
			outframe[i] = (p<<16)+(p<<8)+p+0xFF000000;
			}
		break;
	case 1:		//R
		for (i=0;i<w*h;i++)
			{
			p=(uint32_t)(255.0*sl[i]) & 0xFF;
			outframe[i] = p+0xFF000000;
			}
		break;
	case 2:		//G
		for (i=0;i<w*h;i++)
			{
			p=(uint32_t)(255.0*sl[i]) & 0xFF;
			outframe[i] = (p<<8)+0xFF000000;
			}
		break;
	case 3:		//B
		for (i=0;i<w*h;i++)
			{
			p=(uint32_t)(255.0*sl[i]) & 0xFF;
			outframe[i] = (p<<16)+0xFF000000;
			}
		break;
	case 4:		//ccir rec 601  R-Y   on 50 gray
		for (i=0;i<w*h;i++)
			{
			r=sl[i];
			b=0.5;
			g=(0.5-0.299*r-0.114*b)/0.587;
			ri=(int)(255.0*r);
			gi=(int)(255.0*g);
			bi=(int)(255.0*b);
			outframe[i] = (bi<<16)+(gi<<8)+ri+0xFF000000;
			}
		break;
	case 5:		//ccir rec 601  B-Y   on 50% gray
		for (i=0;i<w*h;i++)
			{
			b=sl[i];
			r=0.5;
			g=(0.5-0.299*r-0.114*b)/0.587;
			ri=(int)(255.0*r);
			gi=(int)(255.0*g);
			bi=(int)(255.0*b);
			outframe[i] = (bi<<16)+(gi<<8)+ri+0xFF000000;
			}
		break;
	case 6:		//ccir rec 709  R-Y   on 50 gray
		for (i=0;i<w*h;i++)
			{
			r=sl[i];
			b=0.5;
			g=(0.5-0.2126*r-0.0722*b)/0.7152;
			ri=(int)(255.0*r);
			gi=(int)(255.0*g);
			bi=(int)(255.0*b);
			outframe[i] = (bi<<16)+(gi<<8)+ri+0xFF000000;
			}
		break;
	case 7:		//ccir rec 709  B-Y   on 50% gray
		for (i=0;i<w*h;i++)
			{
			b=sl[i];
			r=0.5;
			g=(0.5-0.2126*r-0.0722*b)/0.7152;
			ri=(int)(255.0*r);
			gi=(int)(255.0*g);
			bi=(int)(255.0*b);
			outframe[i] = (bi<<16)+(gi<<8)+ri+0xFF000000;
			}
		break;
	default:
		break;
	}

}

//-----------------------------------------------------
//stretch [0...1] to parameter range [min...max] linear
float map_value_forward(double v, float min, float max)
{
return min+(max-min)*v;
}

//-----------------------------------------------------
//collapse from parameter range [min...max] to [0...1] linear
double map_value_backward(float v, float min, float max)
{
return (v-min)/(max-min);
}

//-----------------------------------------------------
//stretch [0...1] to parameter range [min...max] logarithmic
//min and max must be positive!
float map_value_forward_log(double v, float min, float max)
{
float sr,k;

sr=sqrtf(min*max);
k=2.0*log(max/sr);
return sr*expf(k*(v-0.5));
}

//-----------------------------------------------------
//collapse from parameter range [min...max] to [0...1] logarithmic
//min and max must be positive!
double map_value_backward_log(float v, float min, float max)
{
float sr,k;

sr=sqrtf(min*max);
k=2.0*log(max/sr);
return logf(v/sr)/k+0.5;
}

//**************************************************
//obligatory frei0r stuff follows

//------------------------------------------------
//this structure holds an instance of the test_pat_I plugin
typedef struct
{
  unsigned int w;
  unsigned int h;

  int type;
  int chan;
  float amp;
  float pw;
  float tilt;
  int neg;

  float *sl;

} tp_inst_t;

//----------------------------------------------------
int f0r_init()
{
  return 1;
}

//--------------------------------------------------
void f0r_deinit()
{ /* no initialization required */ }

//--------------------------------------------------
void f0r_get_plugin_info(f0r_plugin_info_t* tp_info)
{
  tp_info->name           = "test_pat_I";
  tp_info->author         = "Marko Cebokli";
  tp_info->plugin_type    = F0R_PLUGIN_TYPE_SOURCE;
//  tp_info->plugin_type    = F0R_PLUGIN_TYPE_FILTER;
  tp_info->color_model    = F0R_COLOR_MODEL_RGBA8888;
  tp_info->frei0r_version = FREI0R_MAJOR_VERSION;
  tp_info->major_version  = 0;
  tp_info->minor_version  = 2;
  tp_info->num_params     = 6;
  tp_info->explanation    = "Generates spatial impulse and step test patterns";
}

//--------------------------------------------------
void f0r_get_param_info(f0r_param_info_t* info, int param_index)
{
  switch (param_index)
    {
    case 0:
      info->name        = "Type";
      info->type        = F0R_PARAM_DOUBLE;
      info->explanation = "Type of test pattern"; break;
    case 1:
      info->name	="Channel";
      info->type	= F0R_PARAM_DOUBLE;
      info->explanation = "Into which color channel to draw";
      break;
    case 2:
      info->name	= "Amplitude";
      info->type	= F0R_PARAM_DOUBLE;
      info->explanation = "Amplitude (contrast) of the pattern";
      break;
    case 3:
      info->name        = "Width";
      info->type        = F0R_PARAM_DOUBLE;
      info->explanation = "Width of impulse";
      break;
    case 4:
      info->name	= "Tilt";
      info->type	= F0R_PARAM_DOUBLE;
      info->explanation = "Angle of step function";
      break;
    case 5:
      info->name	= "Negative";
      info->type	= F0R_PARAM_BOOL;
      info->explanation = "Change polarity of impulse/step";
      break;
    }
}

//--------------------------------------------------
f0r_instance_t f0r_construct(unsigned int width, unsigned int height)
{
  tp_inst_t* inst = calloc(1, sizeof(*inst));
  inst->w  = width; 
  inst->h = height;

  inst->type=0;
  inst->chan=0;
  inst->amp=0.8;
  inst->pw=5.0;
  inst->tilt=0.0;
  inst->neg=0;

  inst->sl=(float*)calloc(width*height,sizeof(float));

  pika_p(inst->sl, inst->w, inst->h, inst->pw, inst->amp);

  return (f0r_instance_t)inst;
}

//--------------------------------------------------
void f0r_destruct(f0r_instance_t instance)
{
  tp_inst_t* inst = (tp_inst_t*)instance;

  free(inst->sl);
  free(inst);
}

//--------------------------------------------------
void f0r_set_param_value(f0r_instance_t instance, f0r_param_t param, int param_index)
{
  tp_inst_t* inst = (tp_inst_t*)instance;

  f0r_param_double* p = (f0r_param_double*) param;

  int chg,tmpi;
  float tmpf;

  chg=0;
  switch (param_index)
    {
    case 0:	//type
      tmpf=*((double*)p);
      if (tmpf>=1.0)
        tmpi=(int)tmpf;
      else
        tmpi = map_value_forward(tmpf, 0.0, 5.9999);
      if ((tmpi<0)||(tmpi>5.0)) break;
      if (inst->type != tmpi) chg=1;
      inst->type = tmpi;
      break;
    case 1:	//channel
      tmpf=*((double*)p);
      if (tmpf>=1.0)
        tmpi=(int)tmpf;
      else
        tmpi = map_value_forward(tmpf, 0.0, 7.9999);
      if ((tmpi<0)||(tmpi>7.0)) break;
      if (inst->chan != tmpi) chg=1;
      inst->chan = tmpi;
    case 2:	//amplitude
      tmpf = map_value_forward(*((double*)p), 0.0, 1.0);
      if (inst->amp != tmpf) chg=1;
      inst->amp = tmpf;
      break;
    case 3:	//width
      tmpf = map_value_forward(*((double*)p), 1.0, 100.0);
      if (inst->pw != tmpf) chg=1;
      inst->pw = tmpf;
      break;
    case 4:	//tilt
      tmpf = map_value_forward(*((double*)p), -PI/2.0, PI/2.0);
      if (inst->tilt != tmpf) chg=1;
      inst->tilt = tmpf;
      break;
    case 5:	//negative
      tmpi = map_value_forward(*((double*)p), 0.0, 1.0);
      if (inst->neg != tmpi) chg=1;
      inst->neg = tmpi;
      break;
    }

  if (chg==0) return;

  switch (inst->type)
    {
    case 0:		 //
      pika_p(inst->sl, inst->w, inst->h, inst->pw, inst->amp);
      break;
    case 1:		 //
      pika_o(inst->sl, inst->w, inst->h, inst->pw, inst->amp);
      break;
    case 2:		 //
      crta_p(inst->sl, inst->w, inst->h, inst->pw, inst->amp, inst->tilt);
      break;
    case 3:		 //
      crta(inst->sl, inst->w, inst->h, inst->pw, inst->amp, inst->tilt);
      break;
    case 4:		//
      crta_s(inst->sl, inst->w, inst->h, inst->pw, inst->amp, inst->tilt);
      break;
    case 5:		//
      crta_r(inst->sl, inst->w, inst->h, inst->pw, inst->amp, inst->tilt);
      break;
    default:
      break;
    }

}

//-------------------------------------------------
void f0r_get_param_value(f0r_instance_t instance, f0r_param_t param, int param_index)
{
  tp_inst_t* inst = (tp_inst_t*)instance;

  f0r_param_double* p = (f0r_param_double*) param;

  switch (param_index)
    {
    case 0:	//type
      *p = map_value_backward(inst->type, 0.0, 5.9999);
      break;
    case 1:	//channel
      *p = map_value_backward(inst->chan, 0.0, 7.9999);
      break;
    case 2:	//amplitude
      *p = map_value_backward(inst->amp, 0.0, 1.0);
      break;
    case 3:	//width
      *p = map_value_backward(inst->pw, 1.0, 100.0);
      break;
    case 4:	//tilt
      *p = map_value_backward(inst->tilt, -PI/2.0, PI/2.0);
      break;
    case 5:	//negative
      *p = map_value_backward(inst->neg, 0.0, 1.0);
      break;
    }
}

//---------------------------------------------------
void f0r_update(f0r_instance_t instance, double time, const uint32_t* inframe, uint32_t* outframe)
{

  assert(instance);
  tp_inst_t* inst = (tp_inst_t*)instance;

  float2color(inst->sl, outframe, inst->w , inst->h, inst->chan);

}
