/*
test_pat_L
This frei0r plugin generates test patterns for levels and
linearity checking

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: Levels and Linearity

This plugin draws a set of test patterns, used for checking of
linearity, gamma, contrast, etc.

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_L.c -o test_pat_L.o

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

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

#include "frei0r.h"


double PI=3.14159265358979;


//----------------------------------------------------------
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;

}

//----------------------------------------------------------
//rectangle with gray gradient
//dir:  0=left to right, 1=top to bottom, 2=r to l, 3=b to t
void draw_gradient(float *sl, int w, int h, int x, int y, int wr, int hr, float gray1, float gray2, int dir)
{
int i,j;
int zx,kx,zy,ky;
float g,dg;

if (wr<=1) return;
if (hr<=1) return;
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;
switch (dir)
	{
	case 0:
		dg=(gray2-gray1)/(wr-1);
		g=gray1;
		for (j=zx;j<kx;j++)
			{
			for (i=zy;i<ky;i++)
				sl[w*i+j]=g;
			g=g+dg;
			}
		break;
	case 1:
		dg=(gray2-gray1)/(hr-1);
		g=gray1;
		for (i=zy;i<ky;i++)
			{
			for (j=zx;j<kx;j++)
				sl[w*i+j]=g;
			g=g+dg;
			}
		break;
	case 2:
		dg=(gray1-gray2)/(wr-1);
		g=gray2;
		for (j=zx;j<kx;j++)
			{
			for (i=zy;i<ky;i++)
				sl[w*i+j]=g;
			g=g+dg;
			}
		break;
	case 3:
		dg=(gray1-gray2)/(hr-1);
		g=gray2;
		for (i=zy;i<ky;i++)
			{
			for (j=zx;j<kx;j++)
				sl[w*i+j]=g;
			g=g+dg;
			}
	default:
		break;
	}

}

//-----------------------------------------------------------
//pocasna za velike kroge.....
void draw_circle(float *sl, int w, int h, float ar, int x, int y, int rn, int rz, float gray)
{
int i,j;
int zx,kx,zy,ky;
float rr,rmin,rmax;

zx=x-rz/ar-1;  if (zx<0) zx=0;
zy=y-rz-1;  if (zy<0) zy=0;
kx=x+rz/ar+1;  if (kx>w) kx=w;
ky=y+rz+1;  if (ky>h) ky=h;
rmin=(float)rn;
rmax=(float)rz;
for (i=zy;i<ky;i++)
	for (j=zx;j<kx;j++)
		{
		rr=sqrtf((i-y)*(i-y)+(j-x)*(j-x)*ar*ar);
		if ((rr>=rmin)&&(rr<=rmax)) sl[w*i+j]=gray;
		}

}

//-------------------------------------------------------
//draw one numerical digit, 7-segment style
//v=size in x direction (in y it is 2*v)
//d= number [0...9]
void disp7s(float *sl, int w, int h, int x, int y, int v, int d, float gray)
{
char seg[10]={0xEE,0x24,0xBA,0xB6,0x74,0xD6,0xDE,0xA4,0xFE,0xF6};

if ((d<0)||(d>9)) return;

if ((seg[d]&128)!=0) draw_rectangle(sl,w,h,x,y-2*v,v,1,gray);
if ((seg[d]&64)!=0) draw_rectangle(sl,w,h,x,y-2*v,1,v,gray);
if ((seg[d]&32)!=0) draw_rectangle(sl,w,h,x+v,y-2*v,1,v,gray);
if ((seg[d]&16)!=0) draw_rectangle(sl,w,h,x,y-v,v,1,gray);
if ((seg[d]&8)!=0) draw_rectangle(sl,w,h,x,y-v,1,v,gray);
if ((seg[d]&4)!=0) draw_rectangle(sl,w,h,x+v,y-v,1,v,gray);
if ((seg[d]&2)!=0) draw_rectangle(sl,w,h,x,y,v,1,gray);
}

//----------------------------------------------------------------
//draw a floating point number
//v=size
//n=number
//f=format (as in printf)
void dispF(float *sl, int w, int h, int x, int y, int v, float n, char *f, float gray)
{
char str[64];
int i;

sprintf(str,f,n);
i=0;
while (str[i]!=0)
	{
	if (str[i]=='-')
		draw_rectangle(sl,w,h,x+i*(v+v/3+1),y-v,v,1,gray);
	else
		disp7s(sl,w,h,x+i*(v+v/3+1),y,v,str[i]-48,gray);
	i++;
	}

}

//----------------------------------------------------------
//gray staircase
void stopnice(float *sl, int w, int h)
{
int j,n;
float s;

n=8;
for (j=0;j<n;j++)
	{
	s=(float)j/(float)(n-1);
	draw_rectangle(sl,w,h, j*w/n, 0, w/n, h, s);
	}

}

//----------------------------------------------------------
//gray staircase with contrast check
void stopnice_k(float *sl, int w, int h)
{
int j,n,w1,h1;
float s,s1,s2;

	n=8;
	w1=w/n/3;
	h1=w1; if (h1>h/20) h1=h/20;
	for (j=0;j<n;j++)
		{
		s=((float)j+0.5)/(float)n;
		draw_rectangle(sl,w,h, j*w/n, 0, w/n, h, s);

		s1=s-0.01; if (s1<0.0) s1=0.0;
		s2=s+0.01; if (s2>1.0) s2=1.0;
		draw_rectangle(sl,w,h, j*w/n+w1,1*h/16, w1,h1, s1);
		draw_rectangle(sl,w,h, j*w/n+w1,2*h/16, w1,h1, s2);

		s1=s-0.02; if (s1<0.0) s1=0.0;
		s2=s+0.02; if (s2>1.0) s2=1.0;
		draw_rectangle(sl,w,h, j*w/n+w1,4*h/16, w1,h1, s1);
		draw_rectangle(sl,w,h, j*w/n+w1,5*h/16, w1,h1, s2);

		s1=s-0.05; if (s1<0.0) s1=0.0;
		s2=s+0.05; if (s2>1.0) s2=1.0;
		draw_rectangle(sl,w,h, j*w/n+w1,7*h/16, w1,h1, s1);
		draw_rectangle(sl,w,h, j*w/n+w1,8*h/16, w1,h1, s2);

		s1=s-0.1; if (s1<0.0) s1=0.0;
		s2=s+0.1; if (s2>1.0) s2=1.0;
		draw_rectangle(sl,w,h, j*w/n+w1,10*h/16, w1,h1, s1);
		draw_rectangle(sl,w,h, j*w/n+w1,11*h/16, w1,h1, s2);

		s1=s-0.2; if (s1<0.0) s1=0.0;
		s2=s+0.2; if (s2>1.0) s2=1.0;
		draw_rectangle(sl,w,h, j*w/n+w1,13*h/16, w1,w1, s1);
		draw_rectangle(sl,w,h, j*w/n+w1,14*h/16, w1,w1, s2);
		}

}

//-----------------------------------------------------
//gray gradient
void sivi_klin(float *sl, int w, int h)
{
draw_rectangle(sl,w,h, 0, 0, w/7, h, 0.5);
draw_rectangle(sl,w,h, 6*w/7, 0, w/7, h, 0.5);
draw_gradient(sl,w,h, w/8, 0, 3*w/4, h, 0.0, 1.0, 0);
}

//----------------------------------------------------
//256 grays
void sivine256(float *sl, int w, int h)
{
int i,j,w1,h1;
float s;

draw_rectangle(sl,w,h, 0, 0, w, h, 0.5);
if (w>h) w1=h/20; else w1=w/20;
h1=w1-2;
for (i=0;i<16;i++)
	for (j=0;j<16;j++)
		{
		s=(float)(16*i+j)/255.0;
		draw_rectangle(sl,w,h, (w-h)/2+(j+2)*w1, (i+2)*w1,  h1, h1, s);
		}
}

//------------------------------------------------------
//contrast bands
void trakovi(float *sl, int w, int h)
{
int i,h1;

draw_rectangle(sl,w,h, 0, 0, w, h, 0.5);
h1=h/64;
for (i=0;i<4;i++)
	{
	draw_gradient(sl,w,h, w/8, (7+2*i)*h1, 3*w/4, h1, 0.0, 0.99, 0);
	draw_gradient(sl,w,h, w/8, (8+2*i)*h1, 3*w/4, h1, 0.01, 1.0, 0);
	}
for (i=0;i<4;i++)
	{
	draw_gradient(sl,w,h, w/8, (21+2*i)*h1, 3*w/4, h1, 0.0, 0.98, 0);
	draw_gradient(sl,w,h, w/8, (22+2*i)*h1, 3*w/4, h1, 0.02, 1.0, 0);
	}
for (i=0;i<4;i++)
	{
	draw_gradient(sl,w,h, w/8, (35+2*i)*h1, 3*w/4, h1, 0.0, 0.95, 0);
	draw_gradient(sl,w,h, w/8, (36+2*i)*h1, 3*w/4, h1, 0.05, 1.0, 0);
	}
for (i=0;i<4;i++)
	{
	draw_gradient(sl,w,h, w/8, (49+2*i)*h1, 3*w/4, h1, 0.0, 0.90, 0);
	draw_gradient(sl,w,h, w/8, (50+2*i)*h1, 3*w/4, h1, 0.1, 1.0, 0);
	}

}

//----------------------------------------------------------
void gamatest(float *sl, int w, int h)
{
int i,s,x,y;
float g;

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

//gray patches
for (i=0;i<30;i++)
	{
	s=66+5*i;
	g=1.0/(logf((float)s/255.0)/logf(0.5));
	x=w/4+3*w/16*(i/10);
	y=(i%10+1)*h/12;
	draw_rectangle(sl,w,h,x,y,w/8,h/13,(float)s/255.0);
	s=(s<140)?240:20;
	dispF(sl,w,h, x+w/16-18,y+h/24+4, 6, g, "%4.2f", s/255.0);
	}
//zebra bars
for (i=h/16;i<15*h/16;i++)
	{
	g=(i%2==0)?1.0:0.0;
	draw_rectangle(sl,w,h,3*w/16,i,w/16,1,g);	
	draw_rectangle(sl,w,h,6*w/16,i,w/16,1,g);	
	draw_rectangle(sl,w,h,9*w/16,i,w/16,1,g);	
	draw_rectangle(sl,w,h,12*w/16,i,w/16,1,g);	
	}
//black and white level sidebars
draw_rectangle(sl,w,h,w/16,h/12,w/16,10*h/12,0.0);
draw_rectangle(sl,w,h,14*w/16,h/12,w/16,10*h/12,1.0);
for (i=1;i<11;i++)
	{
	g=(float)i*0.01;
	draw_rectangle(sl,w,h,w/16+w/48,i*h/12+h/36,w/48,h/36,g);
	g=(float)(100-i)*0.01;
	draw_rectangle(sl,w,h,14*w/16+w/48,i*h/12+h/36,w/48,h/36,g);
	}

}

//--------------------------------------------------
void ortikon(float *sl, int w, int h)
{
int i;
float s1,s2;

draw_rectangle(sl,w,h, 0, 0, w, h, 0.6);
//mali krogec
draw_circle(sl,w,h,1.0, 0.3*w, h/8, 0, 10, 0.95);
//crni in beli krog
draw_circle(sl,w,h,1.0, 0.6*w, h/8, 0, 20, 0.95);
draw_circle(sl,w,h,1.0, 0.6*w+40, h/8, 0, 20, 0.05);
//gradient levo
draw_gradient(sl,w,h, 0, h/4, 0.3*w, 3*h/4, 0.84, 0.094, 1);
//svetel trak
draw_rectangle(sl,w,h, 0.13*w, h/4, w/20, 3*h/4, 0.97);
//svetel trak z gradientom
draw_gradient(sl,w,h, 17*w/40,h/4, w/20,3*h/4, 0.97,0.6, 1);
s1=0.9; s2=0.1;
for (i=h/4;i<h;i=i+h/4.5)
	{
	draw_rectangle(sl,w,h,0.6*w,i,h/9,h/9,s2);
	draw_rectangle(sl,w,h,0.6*w+h/9,i,h/9,h/9,s1);
	draw_rectangle(sl,w,h,0.6*w+2*h/9,i,h/9,h/9,s2);
	draw_rectangle(sl,w,h,0.6*w,i+h/9,h/9,h/9,s1);
	draw_rectangle(sl,w,h,0.6*w+h/9,i+h/9,h/9,h/9,s2);
	draw_rectangle(sl,w,h,0.6*w+2*h/9,i+h/9,h/9,h/9,s1);
	}
}



//-----------------------------------------------------
//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_L plugin
typedef struct
{
  unsigned int w;
  unsigned int h;

  int type;
  int chan;

  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_L";
  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  = 1;
  tp_info->num_params     = 2;
  tp_info->explanation    = "Generates linearity checking 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;
    }
}

//--------------------------------------------------
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->sl=(float*)calloc(width*height,sizeof(float));

  stopnice(inst->sl, inst->w, inst->h);

  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, 6.9999);
      if ((tmpi<0)||(tmpi>6.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;
    }

  if (chg==0) return;

  switch (inst->type)
    {
    case 0:		//gray steps
      stopnice(inst->sl, inst->w, inst->h);
      break;
    case 1:		//gray steps with contrast squares
      stopnice_k(inst->sl, inst->w, inst->h);
      break;
    case 2:		//gray gradient
      sivi_klin(inst->sl, inst->w, inst->h);
      break;
    case 3:		//256 gray squares in a 16x16 matrix
      sivine256(inst->sl, inst->w, inst->h);
      break;
    case 4:		//contrast bands
      trakovi(inst->sl, inst->w, inst->h);
      break;
    case 5:		//gama checking chart
      gamatest(inst->sl, inst->w, inst->h);
      break;
    case 6:		//for testing orthicon simulator
      ortikon(inst->sl, inst->w, inst->h);
      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, 6.9999);
      break;
    case 1:	//channel
      *p = map_value_backward(inst->chan, 0.0, 7.9999);
      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);

}
