/* 
 * plugin to make images compatible with ChromaTek 3D glasses.
 *
 * Nicholas Phillips, Oct 1, 2001
 * 
 */

#include <math.h>
#include <limits.h>
#include "plugin.h"

/* ******************** GLOBAL VARIABLES ***************** */


char name[24]= "ChromaTek";

/* structure for buttons, 
 *  butcode      name           default  min  max  0
 */

VarStruct varstr[]= 
{
  LABEL,	"Input: 1 strip",   0.0,    0.0,     0.0, "", 
  NUMSLI|FLO,	"Front ",          -1.0,   -1.0,     1.0, "Distance to Red",
  NUMSLI|FLO,	"Back ",	    1.0,   -1.0,     1.0, "Distance to Blue",
  TOG|INT,	"Auto Range",	    1,	      0,      1,  "Auto ranging",
  TOG|INT,	"Non-linear",	    0,	      0,      1,  "Toggle Non-Linear",
  NUMSLI|FLO,	"Exponent ",	    1.0,   0.25,     2.0, "Non-linear Power"
};

/* The cast struct is for input in the main doit function
   Varstr and Cast must have the same variables in the same order */ 

typedef struct Cast {
  int dummy;			/* because of the 'label' button */
  float front;
  float back;
  int autorange;
  int nonlinear_toggle;
  float nonlinear_exp;
} Cast;


/* cfra: the current frame */

float cfra;

void plugin_seq_doit(Cast *, float, float, int, int, ImBuf *, ImBuf *, ImBuf *, ImBuf *);

/* ******************** Fixed functions ***************** */

int plugin_seq_getversion(void) 
{
  return B_PLUGIN_VERSION;
}

void plugin_but_changed(int but) 
{
}

void plugin_init()
{
}

void plugin_getinfo(PluginInfo *info)
{
  info->name= name;
  info->nvars= sizeof(varstr)/sizeof(VarStruct);
  info->cfra= &cfra;
  
  info->varstr= varstr;
  
  info->init= plugin_init;
  info->seq_doit= (SeqDoit) plugin_seq_doit;
  info->callback= plugin_but_changed;
}

/* ************************************************************
   (Modified from NaN's showzbuf.c sequence plugin)

   Convert a blender Scene-strip image into a 3D image to be viewed
   with ChromaDepth 3D glasses, www.chromatek.com

   These glasses encode the depth via the color:
       red is closest and blue the furthest. Use the Z-buffer to get this info;
       why this only works with Scene-strips.

   1) Scan the zbuf for the largest and smallest values -> scale to [0,1]
   2) For each pixel, convert from RGB to HSV
   3) Preserve the Saturation and Value info and change H to the distance.
	
   TODO: There may be problems with animations, as the min and max distances
     may vary from frame to frame. Fix?

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

/* Needed by rgb2hsv() */
float maxrgb(float r,float g,float b)
{
  float max;
  
  if( r > g)
    max = r;
  else
    max = g;
  if( b > max )
    max = b;
  return( max );
}


/* Needed by rgb2hsv() */
float minrgb(float r,float g,float b)
{
  float min;
  
  if( r < g)
    min = r;
  else
    min = g;
  if( b < min )
    min = b;
  return( min );
}

/* Taken from "Fund'l of 3D Computer Graphics", Alan Watt (1989)
   Assumes (r,g,b) range from 0.0 to 1.0
   Sets h in degrees: 0.0 to 360.;
      s,v in [0.,1.]
*/
void rgb2hsv(float r, float g, float b,
	      float *hout, float *sout, float *vout)
{
  float h,s,v;
  float max_v,min_v,diff,r_dist,g_dist,b_dist;
  float undefined = 0.0;

  max_v = maxrgb(r,g,b);
  min_v = minrgb(r,g,b);
  diff = max_v - min_v;
  v = max_v;

  if( max_v != 0 )
    s = diff/max_v;
  else
    s = 0.0;
  if( s == 0 )
    h = undefined;
  else
    {
      r_dist = (max_v - r)/diff;
      g_dist = (max_v - g)/diff;
      b_dist = (max_v - b)/diff;
      if( r == max_v ) 
	h = b_dist - g_dist;
      else
	if( g == max_v )
	  h = 2 + r_dist - b_dist;
	else
	  if( b == max_v )
	    h = 4 + g_dist - r_dist;
	  else
	    printf("rgb2hsv::How did I get here?\n");
      h *= 60;
      if( h < 0)
	h += 360.0;
    }
  *hout = h;
  *sout = s;
  *vout = v;
}

/* Taken from "Fund'l of 3D Computer Graphics", Alan Watt (1989)
   Assumes H in degrees, s,v in [0.,1.0];
   (r,g,b) range from 0.0 to 1.0
*/
void hsv2rgb(float hin, float s, float v,
	     float *rout, float *gout, float *bout)
{
  float h;
  float r,g,b;
  float f,p,q,t;
  int i;

  h = hin;
  if( s == 0 )
    {
      r = v;
      g = v;
      b = v;
    }
  else
    {
      if(h == 360.)
	h = 0.0;
      h /= 60.;
      i = (int) h;
      f = h - i;
      p = v*(1-s);
      q = v*(1-(s*f));
      t = v*(1-s*(1-f));
      switch(i)
	{
	case 0:
	  r = v;
	  g = t;
	  b = p;
	  break;
	case 1:
	  r = q;
	  g = v;
	  b = p;
	  break;
	case 2:
	  r = p;
	  g = v;
	  b = t;
	  break;
	case 3:
	  r = p;
	  g = q;
	  b = v;
	  break;
	case 4:
	  r = t;
	  g = p;
	  b = v;
	  break;
	case 5:
	  r = v;
	  g = p;
	  b = q;
	  break;
	default:
	  r = 1.0;
	  b = 1.0;
	  b = 1.0;
	  /*
	  printf("hsv2rgb::How did I get here?\n");
	  printf("h: %f, s: %f, v: %f; i:  %d\n",hin,s,v,i);
	  */
	  break;
	}
    }
  *rout = r;
  *gout = g;
  *bout = b;
}

	  
/* Where we do the work */
void plugin_seq_doit(Cast *cast, 
		     float facf0, 
		     float facf1, 
		     int sx, 
		     int sy, 
		     ImBuf *ibuf1, 
		     ImBuf *ibuf2, 
		     ImBuf *out, 
		     ImBuf *use)
{
  int     a;
  int    *rectz;	
  char   *in_rectc;
  char   *out_rectc;
  float   r,g,b;
  float   h,s,v;
  double  z,minz,maxz;
  double  slope,yint;

  if(ibuf1) 
    {
      if(ibuf1->zbuf==0) {
	printf("no zbuf\n");
	return;
      }


      /* Get the range of the zbuf */
      if( cast->autorange ) {
	  a= ibuf1->x*ibuf1->y;
	  rectz= ibuf1->zbuf;
	  minz =  2.0;
	  maxz = -minz;
	  while(a--) {
	    z = ((float)*rectz)/INT_MAX;
	    if( z > maxz ) maxz = z;
	    if( z < minz ) minz = z;
	    rectz++;
	  }
	  cast->front = minz;
	  cast->back  = maxz;
      }
      /* printf("maxz: %lf,minz: %lf\n",maxz,minz); */

      slope = 1./(cast->back - cast->front);
      yint  = cast->front/(cast->front - cast->back);
      /* printf("slope: %f, yint:  %f\n",slope,yint); */


      /* Scan through the input image buffer */
      a= ibuf1->x*ibuf1->y;
      rectz= ibuf1->zbuf;
      in_rectc  = (char *)ibuf1->rect;
      out_rectc = (char *)out->rect;
      while(a--) 
	{
	  /* Scale orginal color values to [0.,1.] */
	  r = in_rectc[0]/255.;
	  g = in_rectc[1]/255.;
	  b = in_rectc[2]/255.;

	  /* Scale distance to [0.,1.] */
	  z = ((float)*rectz)/INT_MAX;
	  z = slope*z + yint;
	  if( z < 0.0 ) z = 0.0;
	  if( z > 1.0 ) z = 1.0;
	  
	  if( cast->nonlinear_toggle && (z > 0.0) )
	    z = exp( (cast->nonlinear_exp)*log(z) );
	  
	  rgb2hsv(r,g,b,&h,&s,&v);
	  /* Hue goes from red for closest to blue for most distant */
	  h = z*240.;
	  hsv2rgb(h,s,v,&r,&g,&b);

	  /* Set the output image */
	  out_rectc[0] = 255*r;
	  out_rectc[1] = 255*g;
	  out_rectc[2] = 255*b;

	  /* preserve the alpha, not checked! */
	  out_rectc[3] = in_rectc[3];

	  /* next image pixel */
	  in_rectc+= 4;
	  out_rectc+= 4;
	  rectz++;
	}
    }
}
