/* 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 3 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, see
 * <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <babl/babl.h>
#include <glib-object.h>

#include "libgimpmath/gimpmath.h"

#include "gimpcolortypes.h"

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



/**
 * SECTION: gimpcolorspace
 * @title: GimpColorSpace
 * @short_description: Utility functions which convert colors between
 *                     different color models.
 *
 * When programming pixel data manipulation functions you will often
 * use algorithms operating on a color model different from the one
 * GIMP uses.  This file provides utility functions to convert colors
 * between different color spaces.
 **/


#define GIMP_HSV_UNDEFINED -1.0
#define GIMP_HSL_UNDEFINED -1.0

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


/*  GimpRGB functions  */


/**
 * gimp_rgb_to_hsv:
 * @rgb: A color value in the RGB colorspace
 * @hsv: The value converted to the HSV colorspace
 *
 * Does a conversion from RGB to HSV (Hue, Saturation,
 * Value) colorspace.
 **/
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;
          if (hsv->h < 0.0)
            hsv->h += 6.0;
        }
      else if (rgb->g == max)
        {
          hsv->h = 2.0 + (rgb->b - rgb->r) / delta;
        }
      else
        {
          hsv->h = 4.0 + (rgb->r - rgb->g) / delta;
        }

      hsv->h /= 6.0;
    }
  else
    {
      hsv->s = 0.0;
      hsv->h = 0.0;
    }

  hsv->a = rgb->a;
}

/**
 * gimp_hsv_to_rgb:
 * @hsv: A color value in the HSV colorspace
 * @rgb: The returned RGB value.
 *
 * Converts a color value from HSV to RGB colorspace
 **/
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;
}


/**
 * gimp_rgb_to_hsl:
 * @rgb: A color value in the RGB colorspace
 * @hsl: The value converted to HSL
 *
 * Convert an RGB color value to a HSL (Hue, Saturation, Lightness)
 * color value.
 **/
void
gimp_rgb_to_hsl (const GimpRGB *rgb,
                 GimpHSL       *hsl)
{
  gdouble max, min, delta;

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

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

  hsl->l = (max + min) / 2.0;

  if (max == min)
    {
      hsl->s = 0.0;
      hsl->h = GIMP_HSL_UNDEFINED;
    }
  else
    {
      if (hsl->l <= 0.5)
        hsl->s = (max - min) / (max + min);
      else
        hsl->s = (max - min) / (2.0 - max - min);

      delta = max - min;

      if (delta == 0.0)
        delta = 1.0;

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

      hsl->h /= 6.0;

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

  hsl->a = rgb->a;
}

static inline gdouble
gimp_hsl_value (gdouble n1,
                gdouble n2,
                gdouble hue)
{
  gdouble val;

  if (hue > 6.0)
    hue -= 6.0;
  else if (hue < 0.0)
    hue += 6.0;

  if (hue < 1.0)
    val = n1 + (n2 - n1) * hue;
  else if (hue < 3.0)
    val = n2;
  else if (hue < 4.0)
    val = n1 + (n2 - n1) * (4.0 - hue);
  else
    val = n1;

  return val;
}


/**
 * gimp_hsl_to_rgb:
 * @hsl: A color value in the HSL colorspace
 * @rgb: The value converted to a value in the RGB colorspace
 *
 * Convert a HSL color value to an RGB color value.
 **/
void
gimp_hsl_to_rgb (const GimpHSL *hsl,
                 GimpRGB       *rgb)
{
  g_return_if_fail (hsl != NULL);
  g_return_if_fail (rgb != NULL);

  if (hsl->s == 0)
    {
      /*  achromatic case  */
      rgb->r = hsl->l;
      rgb->g = hsl->l;
      rgb->b = hsl->l;
    }
  else
    {
      gdouble m1, m2;

      if (hsl->l <= 0.5)
        m2 = hsl->l * (1.0 + hsl->s);
      else
        m2 = hsl->l + hsl->s - hsl->l * hsl->s;

      m1 = 2.0 * hsl->l - m2;

      rgb->r = gimp_hsl_value (m1, m2, hsl->h * 6.0 + 2.0);
      rgb->g = gimp_hsl_value (m1, m2, hsl->h * 6.0);
      rgb->b = gimp_hsl_value (m1, m2, hsl->h * 6.0 - 2.0);
    }

  rgb->a = hsl->a;
}


/**
 * gimp_rgb_to_cmyk:
 * @rgb: A value in the RGB colorspace
 * @pullout: A scaling value (0-1) indicating how much black should be
 *           pulled out
 * @cmyk: The input value naively converted to the CMYK colorspace
 *
 * Does a naive conversion from RGB to CMYK colorspace. A simple
 * formula that doesn't take any color-profiles into account is used.
 * The amount of black pullout how can be controlled via the @pullout
 * parameter. A @pullout value of 0 makes this a conversion to CMY.
 * A value of 1 causes the maximum amount of black to be pulled out.
 **/
void
gimp_rgb_to_cmyk (const GimpRGB  *rgb,
                  gdouble         pullout,
                  GimpCMYK       *cmyk)
{
  gdouble c, m, y, k;

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

  c = 1.0 - rgb->r;
  m = 1.0 - rgb->g;
  y = 1.0 - rgb->b;

  k = 1.0;
  if (c < k)  k = c;
  if (m < k)  k = m;
  if (y < k)  k = y;

  k *= pullout;

  if (k < 1.0)
    {
      cmyk->c = (c - k) / (1.0 - k);
      cmyk->m = (m - k) / (1.0 - k);
      cmyk->y = (y - k) / (1.0 - k);
    }
  else
    {
      cmyk->c = 0.0;
      cmyk->m = 0.0;
      cmyk->y = 0.0;
    }

  cmyk->k = k;
  cmyk->a = rgb->a;
}

/**
 * gimp_cmyk_to_rgb:
 * @cmyk: A color value in the CMYK colorspace
 * @rgb: The value converted to the RGB colorspace
 *
 * Does a simple transformation from the CMYK colorspace to the RGB
 * colorspace, without taking color profiles into account.
 **/
void
gimp_cmyk_to_rgb (const GimpCMYK *cmyk,
                  GimpRGB        *rgb)
{
  gdouble c, m, y, k;

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

  k = cmyk->k;

  if (k < 1.0)
    {
      c = cmyk->c * (1.0 - k) + k;
      m = cmyk->m * (1.0 - k) + k;
      y = cmyk->y * (1.0 - k) + k;
    }
  else
    {
      c = 1.0;
      m = 1.0;
      y = 1.0;
    }

  rgb->r = 1.0 - c;
  rgb->g = 1.0 - m;
  rgb->b = 1.0 - y;
  rgb->a = cmyk->a;
}


#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.
 ****************************************************************************/

/**
 * gimp_rgb_to_hwb:
 * @rgb: A color value in the RGB colorspace
 * @hue: The hue value of the above color, in the range 0 to 6
 * @whiteness: The whiteness value of the above color, in the range 0 to 1
 * @blackness: The blackness value of the above color, in the range 0 to 1
 *
 * 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.
 *
 * RGB are each on [0, 1]. Whiteness and Blackness are returned in the
 * range [0, 1] and H is returned in the range [0, 6]. If W == 1 - B, H is
 * undefined.
 **/
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;
    }
}

/**
 * gimp_hwb_to_rgb:
 * @hue: A hue value, in the range 0 to 6
 * @whiteness: A whiteness value, in the range 0 to 1
 * @blackness: A blackness value, in the range 0 to 1
 * @rgb: The above color converted to the RGB colorspace
 *
 * H is defined in the range [0, 6] or UNDEFINED, B and W are both in the
 * range [0, 1]. The returned RGB values are all in the range [0, 1].
 **/
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  */

/**
 * gimp_rgb_to_hsv_int:
 * @red: The red channel value, returns the Hue channel
 * @green: The green channel value, returns the Saturation channel
 * @blue: The blue channel value, returns the Value channel
 *
 * The arguments are pointers to int representing channel values in
 * the RGB colorspace, and the values pointed to are all in the range
 * [0, 255].
 *
 * The function changes the arguments to point to the HSV value
 * corresponding, with the returned values in the following
 * ranges: H [0, 359], S [0, 255], V [0, 255].
 **/
void
gimp_rgb_to_hsv_int (gint *red,
                     gint *green,
                     gint *blue)
{
  gdouble  r, g, b;
  gdouble  h, s, v;
  gint     min;
  gdouble  delta;

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

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

  delta = v - min;

  if (v == 0.0)
    s = 0.0;
  else
    s = delta / v;

  if (s == 0.0)
    {
      h = 0.0;
    }
  else
    {
      if (r == v)
        h = 60.0 * (g - b) / delta;
      else if (g == v)
        h = 120 + 60.0 * (b - r) / delta;
      else
        h = 240 + 60.0 * (r - g) / delta;

      if (h < 0.0)
        h += 360.0;

      if (h > 360.0)
        h -= 360.0;
    }

  *red   = ROUND (h);
  *green = ROUND (s * 255.0);
  *blue  = ROUND (v);

  /* avoid the ambiguity of returning different values for the same color */
  if (*red == 360)
    *red = 0;
}

/**
 * gimp_hsv_to_rgb_int:
 * @hue: The hue channel, returns the red channel
 * @saturation: The saturation channel, returns the green channel
 * @value: The value channel, returns the blue channel
 *
 * The arguments are pointers to int, with the values pointed to in the
 * following ranges:  H [0, 360], S [0, 255], V [0, 255].
 *
 * The function changes the arguments to point to the RGB value
 * corresponding, with the returned values all in the range [0, 255].
 **/
void
gimp_hsv_to_rgb_int (gint *hue,
                     gint *saturation,
                     gint *value)
{
  gdouble h, s, v, h_temp;
  gdouble f, p, q, t;
  gint i;

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

      if (h == 360)
        h_temp = 0;
      else
        h_temp = h;

      h_temp = h_temp / 60.0;
      i = floor (h_temp);
      f = h_temp - i;
      p = v * (1.0 - s);
      q = v * (1.0 - (s * f));
      t = v * (1.0 - (s * (1.0 - f)));

      switch (i)
        {
        case 0:
          *hue        = ROUND (v * 255.0);
          *saturation = ROUND (t * 255.0);
          *value      = ROUND (p * 255.0);
          break;

        case 1:
          *hue        = ROUND (q * 255.0);
          *saturation = ROUND (v * 255.0);
          *value      = ROUND (p * 255.0);
          break;

        case 2:
          *hue        = ROUND (p * 255.0);
          *saturation = ROUND (v * 255.0);
          *value      = ROUND (t * 255.0);
          break;

        case 3:
          *hue        = ROUND (p * 255.0);
          *saturation = ROUND (q * 255.0);
          *value      = ROUND (v * 255.0);
          break;

        case 4:
          *hue        = ROUND (t * 255.0);
          *saturation = ROUND (p * 255.0);
          *value      = ROUND (v * 255.0);
          break;

        case 5:
          *hue        = ROUND (v * 255.0);
          *saturation = ROUND (p * 255.0);
          *value      = ROUND (q * 255.0);
          break;
        }
    }
}

/**
 * gimp_rgb_to_hsl_int:
 * @red: Red channel, returns Hue channel
 * @green: Green channel, returns Lightness channel
 * @blue: Blue channel, returns Saturation channel
 *
 * The arguments are pointers to int representing channel values in the
 * RGB colorspace, and the values pointed to are all in the range [0, 255].
 *
 * The function changes the arguments to point to the corresponding HLS
 * value with the values pointed to in the following ranges:  H [0, 360],
 * L [0, 255], S [0, 255].
 **/
void
gimp_rgb_to_hsl_int (gint *red,
                     gint *green,
                     gint *blue)
{
  gint    r, g, b;
  gdouble h, s, l;
  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   = ROUND (h);
  *green = ROUND (s);
  *blue  = ROUND (l);
}

/**
 * gimp_rgb_to_l_int:
 * @red: Red channel
 * @green: Green channel
 * @blue: Blue channel
 *
 * Calculates the lightness value of an RGB triplet with the formula
 * L = (max(R, G, B) + min (R, G, B)) / 2
 *
 * Return value: Luminance vaue corresponding to the input RGB value
 **/
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 ROUND ((max + min) / 2.0);
}

static inline gint
gimp_hsl_value_int (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 ROUND (value * 255.0);
}

/**
 * gimp_hsl_to_rgb_int:
 * @hue: Hue channel, returns Red channel
 * @saturation: Saturation channel, returns Green channel
 * @lightness: Lightness channel, returns Blue channel
 *
 * The arguments are pointers to int, with the values pointed to in the
 * following ranges:  H [0, 360], L [0, 255], S [0, 255].
 *
 * The function changes the arguments to point to the RGB value
 * corresponding, with the returned values all in the range [0, 255].
 **/
void
gimp_hsl_to_rgb_int (gint *hue,
                     gint *saturation,
                     gint *lightness)
{
  gdouble h, s, l;

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

  if (s == 0)
    {
      /*  achromatic case  */
      *hue        = l;
      *lightness  = l;
      *saturation = l;
    }
  else
    {
      gdouble m1, m2;

      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_hsl_value_int (m1, m2, h + 85);
      *saturation = gimp_hsl_value_int (m1, m2, h);
      *lightness  = gimp_hsl_value_int (m1, m2, h - 85);
    }
}

/**
 * gimp_rgb_to_cmyk_int:
 * @red:     the red channel; returns the cyan value (0-255)
 * @green:   the green channel; returns the magenta value (0-255)
 * @blue:    the blue channel; returns the yellow value (0-255)
 * @pullout: the percentage of black to pull out (0-100); returns
 *           the black value (0-255)
 *
 * Does a naive conversion from RGB to CMYK colorspace. A simple
 * formula that doesn't take any color-profiles into account is used.
 * The amount of black pullout how can be controlled via the @pullout
 * parameter. A @pullout value of 0 makes this a conversion to CMY.
 * A value of 100 causes the maximum amount of black to be pulled out.
 **/
void
gimp_rgb_to_cmyk_int (gint *red,
                      gint *green,
                      gint *blue,
                      gint *pullout)
{
  gint c, m, y;

  c = 255 - *red;
  m = 255 - *green;
  y = 255 - *blue;

  if (*pullout == 0)
    {
      *red   = c;
      *green = m;
      *blue  = y;
    }
  else
    {
      gint k = 255;

      if (c < k)  k = c;
      if (m < k)  k = m;
      if (y < k)  k = y;

      k = (k * CLAMP (*pullout, 0, 100)) / 100;

      *red   = ((c - k) << 8) / (256 - k);
      *green = ((m - k) << 8) / (256 - k);
      *blue  = ((y - k) << 8) / (256 - k);
      *pullout = k;
    }
}

/**
 * gimp_cmyk_to_rgb_int:
 * @cyan:    the cyan channel; returns the red value (0-255)
 * @magenta: the magenta channel; returns the green value (0-255)
 * @yellow:  the yellow channel; returns the blue value (0-255)
 * @black:   the black channel (0-255); doesn't change
 *
 * Does a naive conversion from CMYK to RGB colorspace. A simple
 * formula that doesn't take any color-profiles into account is used.
 **/
void
gimp_cmyk_to_rgb_int (gint *cyan,
                      gint *magenta,
                      gint *yellow,
                      gint *black)
{
  gint c, m, y, k;

  c = *cyan;
  m = *magenta;
  y = *yellow;
  k = *black;

  if (k)
    {
      c = ((c * (256 - k)) >> 8) + k;
      m = ((m * (256 - k)) >> 8) + k;
      y = ((y * (256 - k)) >> 8) + k;
    }

  *cyan    = 255 - c;
  *magenta = 255 - m;
  *yellow  = 255 - y;
}

/**
 * gimp_rgb_to_hsv4:
 * @rgb:        RGB triplet, rgb[0] is red channel, rgb[1] is green,
 *              rgb[2] is blue (0..255)
 * @hue:        Pointer to hue channel (0..1)
 * @saturation: Pointer to saturation channel (0..1)
 * @value:      Pointer to value channel (0..1)
 **/
void
gimp_rgb_to_hsv4 (const 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;

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

/**
 * gimp_hsv_to_rgb4:
 * @rgb:        RGB triplet, rgb[0] is red channel, rgb[1] is green,
 *              rgb[2] is blue (0..255)
 * @hue:        Hue channel (0..1)
 * @saturation: Saturation channel (0..1)
 * @value:      Value channel (0..1)
 **/
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] = ROUND (hue        * 255.0);
  rgb[1] = ROUND (saturation * 255.0);
  rgb[2] = ROUND (value      * 255.0);
}
