/*
 * CMWrgbhsv.c --
 *
 * Procedures to translate between RGB color space and HSV color space.
 * Courtesy of Ken Fishkin.
 *
 *     *********************************************************************
 *     * Copyright (C) 1985, 1990 Regents of the University of California. *
 *     * Permission to use, copy, modify, and distribute this              *
 *     * software and its documentation for any purpose and without        *
 *     * fee is hereby granted, provided that the above copyright          *
 *     * notice appear in all copies.  The University of California        *
 *     * makes no representations about the suitability of this            *
 *     * software for any purpose.  It is provided "as is" without         *
 *     * express or implied warranty.  Export of this software outside     *
 *     * of the United States of America may require an export license.    *
 *     *********************************************************************
 */

#ifndef lint
static const char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/cmwind/CMWrgbhsv.c,v 1.1.1.1 2008/02/03 20:43:50 tim Exp $";
#endif  /* not lint */

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

#include "utils/magic.h"

/* max of three numbers */
#define	MAX3(a,b,c)	(a > b ? (a > c ? a : c) : ( b > c ?b : c))
/*	and min	*/
#define	MIN3(a,b,c)	(a < b ? (a < c ? a : c) : ( b < c ?b : c))

/*
|-----------------------------------------------------------------------|
|	procedure	:  RGBxHSV					|
|	description	:  given r,g,b, each on [0,1], return hue, sat-	|
|			:  uration, and value, each on [0,1].		|
|			:  returns true <==> hue is defined.		|
|	source		:  Smith, Alvy Ray. "Color Tutorial Notes".	|
|			:  Technical Memo 37, LucasFilm.		|
|			:  hue calc changed a tad, according to Foley	|
|			:  and Van Dam.					|
|-----------------------------------------------------------------------|
*/

bool
RGBxHSV(
    double r,
    double g,
    double b,
    double *h,
    double *s,
    double *v)
{
  double	max, delta;
  double	mr,mg,mb;

  *v = max = MAX3(r,g,b);
  if ( (max == 0) || ( (delta = max - MIN3(r,g,b) ) == 0) ) {
	  /* achromatic */
    *s = 0;
    *h = 0;
    return(FALSE);
  };

  *s = delta/ max;
  mr = (max - r)/delta;
  mg = (max - g)/delta;
  mb = (max - b)/delta;

  if (r == max)
      *h = mb - mg;
  else if ( g == max)
      *h = 2.0 + mr - mb;
  else if ( b == max)
      *h = 4.0 + mg - mr;

  *h /= 6;
  if (*h < 0.0)
      *h += 1.0;
  return(TRUE);
}

/*
|-----------------------------------------------------------------------|
|	procedure	:  HSVxRGB					|
|	description	:  given HSV on [0,1], returns equivalent	|
|			:  RGB on [0,1].				|
|	source		:  Smith, Alvy Ray. "Color Tutorial Notes".	|
|			:  Technical Memo 37, LucasFilm.		|
|-----------------------------------------------------------------------|
*/
#define		SETRGB(rr,gg,bb)	*r=rr;*g=gg;*b=bb

void
HSVxRGB(
    double h,
    double s,
    double v,
    double *r,
    double *g,
    double *b)
{
    double	f,m,n,k;
    int	i;
    double	vs,vsf;

    h *= 6;
    i	= h; 	/* integer part of hue	*/
    f	= h - i;	/* fractional part	*/
    vs	= v*s;
    vsf	= vs*f;
    m	= v - vs;
    n	= v - vsf;
    k	= v - vs + vsf;
    switch(i % 6)  {
      case 0	: SETRGB( v,k,m); break;
      case 1	: SETRGB( n,v,m); break;
      case 2	: SETRGB( m,v,k); break;
      case 3	: SETRGB( m,n,v); break;
      case 4	: SETRGB( k,m,v); break;
      case 5	: SETRGB( v,m,n); break;
    }
}

/*
|-----------------------------------------------------------------------|
|	procedure	:  RGBxHSL					|
|	description	:  converts from RGB to HSL coordinates.	|
|	source		:  '79 graphics core.				|
|-----------------------------------------------------------------------|
*/

bool
RGBxHSL(
    double r,
    double g,
    double b,
    double *h,
    double *s,
    double *l)
{
  double min, max;
  double delta, mr, mg, mb;

  min = MIN3( r,g,b);
  max = MAX3( r,g,b);

  /* Lightness calculation 	*/
  /* source - 79 graphics core	*/
  *l = 0.5 * (max + min);

  if ( (delta = max - min) == 0) {
    /* achromatic	*/
    *s = 0.0;
    *h = 0.0;
    return FALSE;
  }

  /* Saturation calculation */
  /* HSL is a double cone	*/
  if (*l < 0.5)
    *s = (delta)/(max+min);
  else
    *s = (delta)/(2.0-(max+min));

  /* hue calculation	*/
  mr = (max - r)/delta;
  mg = (max - g)/delta;
  mb = (max - b)/delta;


  if (r == max)
      *h = mb - mg;
  else if ( g == max)
      *h = 2.0 + mr - mb;
  else if ( b == max)
      *h = 4.0 + mg - mr;

  *h /= 6;
  if (*h < 0.0)
      *h += 1.0;
  return(TRUE);
}

/*
|-----------------------------------------------------------------------|
|	procedure	:  HSLxRGB					|
|	description	:  converts from HSL, each on [0,1], to RGB,	|
|			:  each on [0,1].				|
|	note		:  the idea is to use s,l to get HSV s,v, and	|
|			:  then use HSVxRGB - this is faster than the	|
|			:  '79 core algorithm.				|
|-----------------------------------------------------------------------|
*/

void
HSLxRGB(
    double h,
    double s,
    double l,
    double *r,
    double *g,
    double *b)
{
  double	min;
  double	v;
  double	f,n,k;
  int	i;
  double	vsf;

  if ( l <= 0.5)
    v = l * (1.0 + s);
  else
    v = l + s - l*s;

  min = 2*l - v;
  if ( (s == 0.0) || ( l == 0.0) || ( l == 1.0 ) ) {
      SETRGB(l,l,l);
      return;
  }
  /* now just use hsvxrgb	*/
  s = ( v - min) / v;

  h *= 6;
  i	= h; 	/* integer part of hue	*/
  f	= h - i;	/* fractional part	*/
  vsf	= v*s*f;
  n	= v - vsf;
  k	= min + vsf;
  switch(i % 6)  {
    case 0	: SETRGB( v,k,min); break;
    case 1	: SETRGB( n,v,min); break;
    case 2	: SETRGB( min,v,k); break;
    case 3	: SETRGB( min,n,v); break;
    case 4	: SETRGB( k,min,v); break;
    case 5	: SETRGB( v,min,n); break;
  }
}

/*
|-----------------------------------------------------------------------|
|	procedure	:  Correct_chromaticity				|
|	description	:  given the white point at location (wx,wy),	|
|			:  and an existing point (x,y), such that	|
|			:  x + y > 1, intersect a line through the	|
|			:  white point and the exisiting point with the	|
|			:  line x+y = 1. side-affects new point.	|
|-----------------------------------------------------------------------|
*/

void
Correct_chromaticity(
    double	*x,
    double	*y,
    double	wx,
    double	wy)
{
    double	oldx,oldy;
    double	slope;
    double	b;

    oldx = *x; oldy = *y;
    slope = ( oldy - wy ) / ( oldx - wx);
    /* find b by eval'ing at white point */
    b = wy - slope*wx;

    *x = ( 1.0 - b ) / ( 1.0 + slope);
    *y = 1.0 - *x;
}

/*
|-----------------------------------------------------------------------|
|	procedure	:  xyz_to_mrgb					|
|	description	:  converts xyz to monitor rgb.			|
|			:  only really correct for mitsubishi mon.	|
|-----------------------------------------------------------------------|
*/

void
xyz_to_mrgb(
    double	x,
    double	y,
    double	z,
    double	*mr,
    double	*mg,
    double	*mb)
{
    *mr =  2.4513*x - 1.2249*y - 0.3237*z;
    *mg = -1.4746*x + 2.5052*y + 0.0596*z;
    *mb =  0.0212*x - 0.2550*y + 1.1487*z;
}

/*
|-----------------------------------------------------------------------|
|	procedure	:  Make_mRGB_Nice				|
|	description	:  make mRGB values lie in 0..1, correcting for	|
|			:  luminance overflow, chrominance irreproduc-	|
|			:  ibility.					|
|	assumptions	:  at most one of mR,mG,mB < 0			|
|-----------------------------------------------------------------------|
*/

void
Make_mRGB_Nice(
    double	*mR,
    double	*mG,
    double	*mB)
{
    double	min,max;
    double	mr, mg, mb;
    double	mY;
    double	white_mr,white_mg,white_mb;

    max = *mR;
    if ( max < *mG) max = *mG;
    if ( max < *mB) max = *mB;
    if ( max > 1.0) { /* luminance overflow, maintain chromaticity */
	*mR /= max;
	*mG /= max;
	*mB /= max;
    }
    min = *mR;
    if ( min > *mG) min = *mG;
    if ( min > *mB) min = *mB;
    if ( min >= 0.0 )
	return;
    /* correct in mrgb space	*/
    mY = *mR + *mG + *mB;
    mr = *mR/mY; mg = *mG / mY; mb = *mB / mY;
    /* correct based on which component is < 0 */
    /* find monitor white point */
    xyz_to_mrgb(1.0/3.0,1.0/3.0,1.0/3.0,&white_mr,&white_mg,&white_mb);
    if ( mr < 0 ) {
	mr = 0.0;
	Correct_chromaticity( &mg, &mb, white_mg,white_mb);
    } else if ( mg < 0 ) {
	mg = 0.0;
	Correct_chromaticity( &mr, &mb, white_mr,white_mb);
    } else if ( mb < 0 ) {
	mb = 0.0;
	Correct_chromaticity( &mr, &mg, white_mr,white_mg);
    }
    *mR = mr*mY;
    *mG = mg*mY;
    *mB = mb*mY;
}

