#!/usr/bin/env slsh
%
% Copyright (C) 2007 John E. Davis
%
% fztopng 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.
%
% fztopng 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.
%
require ("png");
require ("cmdopt");

public variable RGB_METHOD_HSV = 1;
public variable RGB_METHOD_GRADIENT = 2;
public variable RGB_METHOD_XOR = 3;

public variable RGB_Method = RGB_METHOD_HSV;
public variable Min_Saturation = 0.3;
public variable Max_Saturation = 1.0;
public variable Min_Value = 0.6;
public variable Max_Value = 1.0;
public variable Min_Hue = 0.0;
public variable Max_Hue = 360.0;

private variable Version = "0.1.0";

define make_complex_image (zreal, zimag)
{
   variable n = length (zreal);
   variable m = length (zimag);
   variable z = Complex_Type[m,n];
   
   zimag *= 1i;
   variable i;
   _for i (0, n-1, 1)
     z[*,i] = zimag;
   
   _for i (0, m-1, 1)
     z[i,*] += zreal;

   return z;
}

private define arg(z)
{
   return atan2 (Imag(z), Real(__tmp(z)));    %  runs from -PI to PI
}

% returns a value 0->360
private define hue (z)
{
   return ((arg(__tmp(z)) * (180.0/PI)) + 360.0) mod 360.0;
}

% returns a value 0->1
private define saturation (z)
{
   return (2/PI)*atan(abs(2.0*z));
}

private define value (z)
{
   return (1.0 - (2/PI)*atan(abs(z/8.0)));
}

private define z_to_hsv (z)
{
   %return hue(z), 

   variable h, s, v;

   h = hue (z);
   % model 1
   if (1)
     {
	variable r = abs(__tmp(z));
	r = log(1.0+__tmp(r));
	v = ((1.0+sin((2*PI)*r))/2.0);
	s = ((1.0+cos((2*PI)*r))/2.0);
     }
   else
     {
	s = saturation(z);
	v = value(z);
     }

   variable sat_min = Min_Saturation, sat_max = Max_Saturation;
   variable val_min = Min_Value, val_max = Max_Value;

   v = val_min + (val_max - val_min) * __tmp(v);
   s = sat_min + (sat_max - sat_min) * __tmp(s);

   % Min_Hue could be negative.  So do it this way
   h = ((Min_Hue + 360.0) + (Max_Hue-Min_Hue) * __tmp(h)/360.0) mod 360.0;
   return h, s, v;
}
   
private define rgb_to_rgb (r, g, b)
{
   return ((r & 0xFF) shl 16) + ((g & 0xFF) shl 8) + (b & 0xFF);
}

private define uchar (x)
{
   return typecast (x, UChar_Type);
}

private define z_to_rgb_via_hsv (z)
{
   variable h, s, v;
   (h, s, v) = z_to_hsv (__tmp(z));

   h = __tmp(h)/60.0;
   v = 255.99999 * __tmp(v);

   variable i = int(floor(h)) mod 6;
   variable f = h - i;
   variable p = uchar (v * (1.0 - s));
   variable q = uchar (v * (1.0 - (s * f)));
   variable t = uchar (v * (1.0 - (s * (1.0 - f))));
   v = uchar (v);

   variable r = @p;
   variable g = @p;
   variable b = @p;

   variable j = where (i == 0);
   r[j] = v[j]; g[j] = t[j]; b[j] = p[j];
   
   j = where (i == 1);
   r[j] = q[j]; g[j] = v[j]; b[j] = p[j];

   j = where (i == 2);
   r[j] = p[j]; g[j] = v[j]; b[j] = t[j];
   
   j = where (i == 3);
   r[j] = p[j]; g[j] = q[j]; b[j] = v[j];
   
   j = where (i == 4);
   r[j] = t[j]; g[j] = p[j]; b[j] = v[j];

   j = where (i == 5);
   r[j] = v[j]; g[j] = p[j]; b[j] = q[j];

   return ((r & 0xFF) shl 16) + ((g & 0xFF) shl 8) + (b & 0xFF);
}

private define z_to_rgb_via_grad (z, a0, a1)
{
   variable c0, c1;
   variable t = hue(__tmp(z))/360.0;
   variable mask = 0xFF0000, n = 16;
   variable c = 0;
   loop (3)
     {
	c0 = ((a0 & mask) shr n);
	c1 = ((a1 & mask) shr n);
	c |= (uchar ((c1-c0)*t + c0) shl n);
	n -= 8;
	mask = mask shr 8;
     }
   return c;
}

private variable f_of_z_func;
private define iterate (z, n)
{
   loop (n)
     z = (@f_of_z_func) (__tmp(z));
   return z;
}

private define exit_usage ()
{
   () = fprintf (stderr, "Version %S Usage: %s [options] -f func | func.sl\n", Version, __argv[0]);
   foreach 
     (["Options:\n",
       " -x|--real xlo:xhi:#N        Grid for x=Real(z) (default: -2:2:#512)\n",
       " -y|--imag ylo:yhi:#N        Grid for y=Imag(z) (default: -2:2:#512)\n",
       " --iter N                    Iterate N type (default=1)\n",
       " --hsv                       Use HSV method (default)\n",
       " --hue=float,float           Min/Max values of Hue ([0->360])\n",
       " --sat=float,float           Min/Max values of Saturation ([0->1])\n",
       " --val=float,float           Min/Max values of Value ([0->1])\n",
       " --grad                      Use gradient method\n",
       " --rgb=int,int               Min/Max RGB values for gradient method\n",
       " -o file.png                 Output file (default: func.png)\n",
       " -h|--help                   This message\n",
       "\n",
       "Note: The file func.sl must contain a function called f_of_z.\n",
       "If the -f expr form is used, it must involve z, e.g., -f sin(z)\n"
      ])
     {
	variable line = ();
	() =fputs (line, stderr);
     }
   exit (1);
}

define slsh_main ()
{
   variable prefs = struct
     {
	xgrid = [-1.5:1.5:#512],
	ygrid = [-1.5:1.5:#512], 
	method = RGB_METHOD_HSV,
	iter = 0,
	hue = [0.0, 360.0],
	saturation = [0.3, 1.0],
	value = [0.6, 1.0],
	rgb = [0x4C4CFF, 0xFFFF00],
     };

   variable xgrid = NULL, ygrid = NULL, method = NULL, iter = NULL,
     rgb = NULL, sat = NULL, val = NULL, hue = NULL;

   variable outfile = NULL;
   variable func = NULL, func_file = NULL;
   variable c = cmdopt_new ();
   c.add ("x|real", &xgrid; type="str");
   c.add ("y|imag", &ygrid; type="str");
   c.add ("iter", &iter; type="int");
   c.add ("xor", &method; default=RGB_METHOD_XOR);
   c.add ("hsv", &method; default=RGB_METHOD_HSV);
   c.add ("grad", &method; default=RGB_METHOD_GRADIENT);
   c.add ("rgb", &rgb; type="str");
   c.add ("sat", &sat; type="str");
   c.add ("val", &val; type="str");
   c.add ("hue", &hue; type="str");
   c.add ("o", &outfile; type="str");
   c.add ("f", &func; type="str");
   variable i = c.process (__argv, 1);
   
   if (i == __argc)
     {
	if (func == NULL)
	  exit_usage ();
     }
   else if (i + 1 != __argc)
     exit_usage ();
   else func_file = __argv[i];

   if (outfile == NULL)
     {
	if (func_file == NULL)
	  {
	     () = fprintf (stderr, "No output png file specified\n");
	     exit (1);
	  }
	outfile = path_basename_sans_extname (func_file) + ".png";
     }
   
   if (func_file != NULL)
     {
	ifnot (path_is_absolute (func_file))
	  func_file = path_concat (getcwd (), func_file);
	if (NULL == stat_file (func_file))
	  {
	     () = fprintf (stderr, "Function file %s does not exist\n", func_file);
	     exit (1);
	  }
	try
	  {
	     () = evalfile (func_file);
	  }
	catch AnyError: 
	  {
	     () = fprintf (stderr, "An error occured evaluating %s:\n\n", func_file);
	     throw;
	  }
     }
   if (func != NULL)
     {
	try
	  {
	     eval ("define f_of_z(z) { $func; }"$);
	  }
	catch AnyError:
	  {
	     () = fprintf (stderr, "An error occured evaluating %s:\n\n", func);
	     throw;
	  }
     }

   f_of_z_func = __get_reference ("f_of_z");
   if (f_of_z_func == NULL)
     {
	() = fprintf (stderr, "%s did not define a public function called f_of_z\n", func_file);
	exit (1);
     }

   variable set_prefs = __get_reference ("set_options");
   if (set_prefs != NULL)
     (@set_prefs)(prefs);

   if (xgrid == NULL) xgrid = prefs.xgrid;
   if (ygrid == NULL) ygrid = prefs.ygrid;
   if (rgb == NULL) rgb = prefs.rgb;
   if (hue == NULL) hue = prefs.hue;
   if (sat == NULL) sat = prefs.saturation;
   if (val == NULL) val = prefs.value;
   if (method == NULL) method = prefs.method;
   if (iter == NULL) iter = prefs.iter;

   try
     {
	if (Array_Type != typeof(xgrid)) xgrid = eval ("[$xgrid]"$);
	if (Array_Type != typeof(ygrid)) ygrid = eval ("[$ygrid]"$);
	if (Array_Type != typeof(rgb)) rgb = eval ("[$rgb]"$);
	if (Array_Type != typeof(hue)) hue = eval ("[$hue]"$);
	if (Array_Type != typeof(sat)) sat = eval ("[$sat]"$);
	if (Array_Type != typeof(val)) val = eval ("[$val]"$);
     }
   catch AnyError:
     {
	() = fprintf (stderr, "Unable to expressions\n");
	exit (1);
     }

   try
     {
	Min_Saturation = sat[0];
	Max_Saturation = sat[-1];
	Min_Value = val[0];
	Max_Value = val[-1];
	Min_Hue = hue[0];
	Max_Hue = hue[-1];
	if ((_typeof (rgb) != Int_Type) || (length(rgb) != 2))
	  {
	     () = fprintf (stderr, "Expecting 2 element integer arrays for the rgb option\n");
	     exit (1);
	  }
     }
   catch AnyError:
     {
	() = fprintf (stderr, "Expecting 2 element arrays for hue, sat, val, and rgb options\n");
	exit (1);
     }

   variable z = make_complex_image (xgrid, ygrid);
   z = iterate (__tmp(z), iter+1);

   if (method == RGB_METHOD_XOR)
     {
	rgb = (z_to_rgb_via_grad (z, rgb[0], rgb[1]) xor z_to_rgb_via_hsv(__tmp(z)));
     }
   else if (method == RGB_METHOD_HSV)
     rgb = z_to_rgb_via_hsv(__tmp(z));
   else
     rgb = z_to_rgb_via_grad (z, rgb[0], rgb[1]);

   png_write_flipped (outfile, rgb);
}
