/* LIBGIMP - The GIMP Library 
 * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball                
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 * 
 * This library 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  
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "config.h"

#include <glib.h>

#include "libgimpmath/gimpmath.h"

#include "gimpcolortypes.h"

#include "gimpcolorspace.h"
#include "gimprgb.h"
#include "gimphsv.h"



#define GIMP_HSV_UNDEFINED -1.0
#define GIMP_HSL_UNDEFINED -1.0

/*********************************
 *   color conversion routines   *
 *********************************/


/*  GimpRGB functions  */

void
gimp_rgb_to_hsv (const GimpRGB *rgb,
		 GimpHSV       *hsv)
{
  gdouble max, min, delta;

  g_return_if_fail (rgb != NULL);
  g_return_if_fail (hsv != NULL);

  max = gimp_rgb_max (rgb);
  min = gimp_rgb_min (rgb);

  hsv->v = max;
  delta = max - min;

  if (delta > 0.0001)
    {
      hsv->s = delta / max;

      if (rgb->r == max)
        {
          hsv->h = (rgb->g - rgb->b) / delta;
        }
      else if (rgb->g == max)
        {
          hsv->h = 2.0 + (rgb->b - rgb->r) / delta;
        }
      else if (rgb->b == max)
        {
          hsv->h = 4.0 + (rgb->r - rgb->g) / delta;
        }

      hsv->h /= 6.0;

      if (hsv->h < 0.0)
        hsv->h += 1.0;
      else if (hsv->h > 1.0)
	hsv->h -= 1.0;
    }
  else
    {
      hsv->s = 0.0;
      hsv->h = 0.0;
    }

  hsv->a = rgb->a;
}

void
gimp_hsv_to_rgb (const GimpHSV *hsv,
		 GimpRGB       *rgb)
{
  gint    i;
  gdouble f, w, q, t;

  gdouble hue;

  g_return_if_fail (rgb != NULL);
  g_return_if_fail (hsv != NULL);

  if (hsv->s == 0.0)
    {
      rgb->r = hsv->v;
      rgb->g = hsv->v;
      rgb->b = hsv->v;
    }
  else
    {
      hue = hsv->h;

      if (hue == 1.0)
        hue = 0.0;

      hue *= 6.0;

      i = (gint) hue;
      f = hue - i;
      w = hsv->v * (1.0 - hsv->s);
      q = hsv->v * (1.0 - (hsv->s * f));
      t = hsv->v * (1.0 - (hsv->s * (1.0 - f)));

      switch (i)
        {
        case 0:
          rgb->r = hsv->v;
          rgb->g = t;
          rgb->b = w;
          break;
        case 1:
          rgb->r = q;
          rgb->g = hsv->v;
          rgb->b = w;
          break;
        case 2:
          rgb->r = w;
          rgb->g = hsv->v;
          rgb->b = t;
          break;
        case 3:
          rgb->r = w;
          rgb->g = q;
          rgb->b = hsv->v;
          break;
        case 4:
          rgb->r = t;
          rgb->g = w;
          rgb->b = hsv->v;
          break;
        case 5:
          rgb->r = hsv->v;
          rgb->g = w;
          rgb->b = q;
          break;
        }
    }

  rgb->a = hsv->a;
}

void
gimp_rgb_to_hsl (const GimpRGB *rgb,
		 gdouble       *hue,
		 gdouble       *saturation,
		 gdouble       *lightness)
{
  gdouble max, min, delta;

  g_return_if_fail (rgb != NULL);
  g_return_if_fail (hue != NULL);
  g_return_if_fail (saturation != NULL);
  g_return_if_fail (lightness != NULL);

  max = gimp_rgb_max (rgb);
  min = gimp_rgb_min (rgb);

  *lightness = (max + min) / 2.0;

  if (max == min)
    {
      *saturation = 0.0;
      *hue = GIMP_HSL_UNDEFINED;
    }
  else
    {
      if (*lightness <= 0.5)
        *saturation = (max - min) / (max + min);
      else
        *saturation = (max - min) / (2.0 - max - min);

      delta = max - min;

      if (delta == 0.0)
	delta = 1.0;

      if (rgb->r == max)
        {
          *hue = (rgb->g - rgb->b) / delta;
        }
      else if (rgb->g == max)
        {
          *hue = 2.0 + (rgb->b - rgb->r) / delta;
        }
      else if (rgb->b == max)
        {
          *hue = 4.0 + (rgb->r - rgb->g) / delta;
        }

      *hue = *hue * 60.0;

      if (*hue < 0.0)
        *hue = *hue + 360.0;
    }
}

static gdouble
_gimp_color_value (gdouble n1,
		   gdouble n2,
		   gdouble hue)
{
  gdouble val;

  if (hue > 360.0)
    hue = hue - 360.0;
  else if (hue < 0.0)
    hue = hue + 360.0;
  if (hue < 60.0)
    val = n1 + (n2 - n1) * hue / 60.0;
  else if (hue < 180.0)
    val = n2;
  else if (hue < 240.0)
    val = n1 + (n2 - n1) * (240.0 - hue) / 60.0;
  else
    val = n1;

  return val;
}

void
gimp_hsl_to_rgb (gdouble  hue,
		 gdouble  saturation,
		 gdouble  lightness,
		 GimpRGB *rgb)
{
  gdouble m1, m2;

  g_return_if_fail (rgb != NULL);

  if (lightness <= 0.5)
    m2 = lightness * (1.0 + saturation);
  else
    m2 = lightness + saturation - lightness * saturation;
  m1 = 2.0 * lightness - m2;

  if (saturation == 0)
    {
      if (hue == GIMP_HSV_UNDEFINED)
        rgb->r = rgb->g = rgb->b = 1.0;
    }
  else
    {
      rgb->r = _gimp_color_value (m1, m2, hue + 120.0);
      rgb->g = _gimp_color_value (m1, m2, hue);
      rgb->b = _gimp_color_value (m1, m2, hue - 120.0);
    }
}

#define GIMP_RETURN_RGB(x, y, z) { rgb->r = x; rgb->g = y; rgb->b = z; return; }

/*****************************************************************************
 * Theoretically, hue 0 (pure red) is identical to hue 6 in these transforms.
 * Pure red always maps to 6 in this implementation. Therefore UNDEFINED can
 * be defined as 0 in situations where only unsigned numbers are desired.
 *****************************************************************************/

void
gimp_rgb_to_hwb (const GimpRGB *rgb,
		 gdouble       *hue,
		 gdouble       *whiteness,
		 gdouble       *blackness)
{
  /* RGB are each on [0, 1]. W and B are returned on [0, 1] and H is        */
  /* returned on [0, 6]. Exception: H is returned UNDEFINED if W ==  1 - B. */
  /* ====================================================================== */

  gdouble R = rgb->r, G = rgb->g, B = rgb->b, w, v, b, f;
  gint i;

  w = gimp_rgb_min (rgb);
  v = gimp_rgb_max (rgb);
  b = 1.0 - v;

  if (v == w)
    {
      *hue = GIMP_HSV_UNDEFINED;
      *whiteness = w;
      *blackness = b;
    }
  else
    {
      f = (R == w) ? G - B : ((G == w) ? B - R : R - G);
      i = (R == w) ? 3.0 : ((G == w) ? 5.0 : 1.0);
    
      *hue = (360.0 / 6.0) * (i - f / (v - w));
      *whiteness = w;
      *blackness = b;
    }
}

void
gimp_hwb_to_rgb (gdouble  hue,
		 gdouble  whiteness,
		 gdouble  blackness,
		 GimpRGB *rgb)
{
  /* H is given on [0, 6] or UNDEFINED. whiteness and
   * blackness are given on [0, 1].
   * RGB are each returned on [0, 1].
   */
  
  gdouble h = hue, w = whiteness, b = blackness, v, n, f;
  gint    i;

  h = 6.0 * h/ 360.0;

  v = 1.0 - b;
  if (h == GIMP_HSV_UNDEFINED)
    {
      rgb->r = v;
      rgb->g = v;
      rgb->b = v;
    }
  else
    {
      i = floor (h);
      f = h - i;

      if (i & 1)
	f = 1.0 - f;  /* if i is odd */

      n = w + f * (v - w);     /* linear interpolation between w and v */
    
      switch (i)
        {
          case 6:
          case 0: GIMP_RETURN_RGB (v, n, w);
            break;
          case 1: GIMP_RETURN_RGB (n, v, w);
            break;
          case 2: GIMP_RETURN_RGB (w, v, n);
            break;
          case 3: GIMP_RETURN_RGB (w, n, v);
            break;
          case 4: GIMP_RETURN_RGB (n, w, v);
            break;
          case 5: GIMP_RETURN_RGB (v, w, n);
            break;
        }
    }

}


/*  gint functions  */

void
gimp_rgb_to_hsv_int (gint *red,
		     gint *green,
		     gint *blue)
{
  gint    r, g, b;
  gdouble h, s, v;
  gint    min, max;
  gint    delta;

  h = 0.0;

  r = *red;
  g = *green;
  b = *blue;

  if (r > g)
    {
      max = MAX (r, b);
      min = MIN (g, b);
    }
  else
    {
      max = MAX (g, b);
      min = MIN (r, b);
    }

  v = max;

  if (max != 0)
    s = ((max - min) * 255) / (gdouble) max;
  else
    s = 0;

  if (s == 0)
    h = 0;
  else
    {
      delta = max - min;
      if (r == max)
	h = (g - b) / (gdouble) delta;
      else if (g == max)
	h = 2 + (b - r) / (gdouble) delta;
      else if (b == max)
	h = 4 + (r - g) / (gdouble) delta;
      h *= 42.5;

      if (h < 0)
	h += 255;
      if (h > 255)
	h -= 255;
    }

  *red   = h;
  *green = s;
  *blue  = v;
}

void
gimp_hsv_to_rgb_int (gint *hue,
		     gint *saturation,
		     gint *value)
{
  gdouble h, s, v;
  gdouble f, p, q, t;

  if (*saturation == 0)
    {
      *hue        = *value;
      *saturation = *value;
      *value      = *value;
    }
  else
    {
      h = *hue * 6.0  / 255.0;
      s = *saturation / 255.0;
      v = *value      / 255.0;

      f = h - (gint) h;
      p = v * (1.0 - s);
      q = v * (1.0 - (s * f));
      t = v * (1.0 - (s * (1.0 - f)));

      switch ((gint) h)
	{
	case 0:
	  *hue        = v * 255;
	  *saturation = t * 255;
	  *value      = p * 255;
	  break;

	case 1:
	  *hue        = q * 255;
	  *saturation = v * 255;
	  *value      = p * 255;
	  break;

	case 2:
	  *hue        = p * 255;
	  *saturation = v * 255;
	  *value      = t * 255;
	  break;

	case 3:
	  *hue        = p * 255;
	  *saturation = q * 255;
	  *value      = v * 255;
	  break;

	case 4:
	  *hue        = t * 255;
	  *saturation = p * 255;
	  *value      = v * 255;
	  break;

	case 5:
	  *hue        = v * 255;
	  *saturation = p * 255;
	  *value      = q * 255;
	  break;
	}
    }
}

void
gimp_rgb_to_hls_int (gint *red,
		     gint *green,
		     gint *blue)
{
  gint    r, g, b;
  gdouble h, l, s;
  gint    min, max;
  gint    delta;

  r = *red;
  g = *green;
  b = *blue;

  if (r > g)
    {
      max = MAX (r, b);
      min = MIN (g, b);
    }
  else
    {
      max = MAX (g, b);
      min = MIN (r, b);
    }

  l = (max + min) / 2.0;

  if (max == min)
    {
      s = 0.0;
      h = 0.0;
    }
  else
    {
      delta = (max - min);

      if (l < 128)
	s = 255 * (gdouble) delta / (gdouble) (max + min);
      else
	s = 255 * (gdouble) delta / (gdouble) (511 - max - min);

      if (r == max)
	h = (g - b) / (gdouble) delta;
      else if (g == max)
	h = 2 + (b - r) / (gdouble) delta;
      else
	h = 4 + (r - g) / (gdouble) delta;

      h = h * 42.5;

      if (h < 0)
	h += 255;
      else if (h > 255)
	h -= 255;
    }

  *red   = h;
  *green = l;
  *blue  = s;
}

gint
gimp_rgb_to_l_int (gint red,
		   gint green,
		   gint blue)
{
  gint min, max;

  if (red > green)
    {
      max = MAX (red,   blue);
      min = MIN (green, blue);
    }
  else
    {
      max = MAX (green, blue);
      min = MIN (red,   blue);
    }

  return (max + min) / 2.0;
}

static gint
gimp_hls_value (gdouble n1,
		gdouble n2,
		gdouble hue)
{
  gdouble value;

  if (hue > 255)
    hue -= 255;
  else if (hue < 0)
    hue += 255;
  if (hue < 42.5)
    value = n1 + (n2 - n1) * (hue / 42.5);
  else if (hue < 127.5)
    value = n2;
  else if (hue < 170)
    value = n1 + (n2 - n1) * ((170 - hue) / 42.5);
  else
    value = n1;

  return (gint) (value * 255);
}

void
gimp_hls_to_rgb_int (gint *hue,
		     gint *lightness,
		     gint *saturation)
{
  gdouble h, l, s;
  gdouble m1, m2;

  h = *hue;
  l = *lightness;
  s = *saturation;

  if (s == 0)
    {
      /*  achromatic case  */
      *hue        = l;
      *lightness  = l;
      *saturation = l;
    }
  else
    {
      if (l < 128)
	m2 = (l * (255 + s)) / 65025.0;
      else
	m2 = (l + s - (l * s) / 255.0) / 255.0;

      m1 = (l / 127.5) - m2;

      /*  chromatic case  */
      *hue        = gimp_hls_value (m1, m2, h + 85);
      *lightness  = gimp_hls_value (m1, m2, h);
      *saturation = gimp_hls_value (m1, m2, h - 85);
    }
}

void
gimp_rgb_to_hsv_double (gdouble *red,
			gdouble *green,
			gdouble *blue)
{
  gdouble r, g, b;
  gdouble h, s, v;
  gdouble min, max;
  gdouble delta;

  r = *red;
  g = *green;
  b = *blue;

  h = 0.0; /* Shut up -Wall */

  if (r > g)
    {
      max = MAX (r, b);
      min = MIN (g, b);
    }
  else
    {
      max = MAX (g, b);
      min = MIN (r, b);
    }

  v = max;

  if (max != 0.0)
    s = (max - min) / max;
  else
    s = 0.0;

  if (s == 0.0)
    {
      h = 0.0;
    }
  else
    {
      delta = max - min;

      if (delta == 0.0)
	delta = 1.0;

      if (r == max)
	h = (g - b) / delta;
      else if (g == max)
	h = 2 + (b - r) / delta;
      else if (b == max)
	h = 4 + (r - g) / delta;

      h /= 6.0;

      if (h < 0.0)
	h += 1.0;
      else if (h > 1.0)
	h -= 1.0;
    }

  *red   = h;
  *green = s;
  *blue  = v;
}

void
gimp_hsv_to_rgb_double (gdouble *hue,
			gdouble *saturation,
			gdouble *value)
{
  gdouble h, s, v;
  gdouble f, p, q, t;

  if (*saturation == 0.0)
    {
      *hue        = *value;
      *saturation = *value;
      *value      = *value;
    }
  else
    {
      h = *hue * 6.0;
      s = *saturation;
      v = *value;

      if (h == 6.0)
	h = 0.0;

      f = h - (gint) h;
      p = v * (1.0 - s);
      q = v * (1.0 - s * f);
      t = v * (1.0 - s * (1.0 - f));

      switch ((gint) h)
	{
	case 0:
	  *hue        = v;
	  *saturation = t;
	  *value      = p;
	  break;

	case 1:
	  *hue        = q;
	  *saturation = v;
	  *value      = p;
	  break;

	case 2:
	  *hue        = p;
	  *saturation = v;
	  *value      = t;
	  break;

	case 3:
	  *hue        = p;
	  *saturation = q;
	  *value      = v;
	  break;

	case 4:
	  *hue        = t;
	  *saturation = p;
	  *value      = v;
	  break;

	case 5:
	  *hue        = v;
	  *saturation = p;
	  *value      = q;
	  break;
	}
    }
}

void
gimp_rgb_to_hsv4 (guchar  *rgb, 
		  gdouble *hue, 
		  gdouble *saturation, 
		  gdouble *value)
{
  gdouble red, green, blue;
  gdouble h, s, v;
  gdouble min, max;
  gdouble delta;

  red   = rgb[0] / 255.0;
  green = rgb[1] / 255.0;
  blue  = rgb[2] / 255.0;

  h = 0.0; /* Shut up -Wall */

  if (red > green)
    {
      max = MAX (red,   blue);
      min = MIN (green, blue);
    }
  else
    {
      max = MAX (green, blue);
      min = MIN (red,   blue);
    }

  v = max;

  if (max != 0.0)
    s = (max - min) / max;
  else
    s = 0.0;

  if (s == 0.0)
    h = 0.0;
  else
    {
      delta = max - min;

      if (delta == 0.0)
	delta = 1.0;

      if (red == max)
	h = (green - blue) / delta;
      else if (green == max)
	h = 2 + (blue - red) / delta;
      else if (blue == max)
	h = 4 + (red - green) / delta;

      h /= 6.0;

      if (h < 0.0)
	h += 1.0;
      else if (h > 1.0)
	h -= 1.0;
    }

  *hue        = h;
  *saturation = s;
  *value      = v;
}

void
gimp_hsv_to_rgb4 (guchar  *rgb, 
		  gdouble  hue,
		  gdouble  saturation,
		  gdouble  value)
{
  gdouble h, s, v;
  gdouble f, p, q, t;

  if (saturation == 0.0)
    {
      hue        = value;
      saturation = value;
      value      = value;
    }
  else
    {
      h = hue * 6.0;
      s = saturation;
      v = value;

      if (h == 6.0)
	h = 0.0;

      f = h - (gint) h;
      p = v * (1.0 - s);
      q = v * (1.0 - s * f);
      t = v * (1.0 - s * (1.0 - f));

      switch ((int) h)
	{
	case 0:
	  hue        = v;
	  saturation = t;
	  value      = p;
	  break;

	case 1:
	  hue        = q;
	  saturation = v;
	  value      = p;
	  break;

	case 2:
	  hue        = p;
	  saturation = v;
	  value      = t;
	  break;

	case 3:
	  hue        = p;
	  saturation = q;
	  value      = v;
	  break;

	case 4:
	  hue        = t;
	  saturation = p;
	  value      = v;
	  break;

	case 5:
	  hue        = v;
	  saturation = p;
	  value      = q;
	  break;
	}
    }

  rgb[0] = hue        * 255;
  rgb[1] = saturation * 255;
  rgb[2] = value      * 255;
}
