
/* Copyright (C) 1992  AHPCRC, Univeristy of Minnesota
 *
 * This program 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.
 *
 * This program 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program in a file named 'Copying'; if not, write to
 * the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139.
 */

/* Author:
 *	Ken Chin-Purcell (ken@ahpcrc.umn.edu)
 *	Army High Performance Computing Research Center (AHPCRC)
 *	Univeristy of Minnesota
 *
 * $Header: /usr/people/ken/gvl/icol/RCS/rgb.c,v 2.1 92/10/19 16:59:34 ken Exp $
 *
 * $Log:	rgb.c,v $
 * Revision 2.1  92/10/19  16:59:34  ken
 * *** empty log message ***
 * 
 * Revision 1.1  92/10/19  16:58:13  ken
 * Initial revision
 * 
 */

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

#include "util.h"
#include "rgb.h"


/* The following are needed to get around specifying floating point
 * constants for Ultrix cc (i.e. 1.0f not allowed),  Yuck.
 * For speed, we don't want to inline double constants.
 */
static float	F_ZERO	= 0;
static float	F_ONE	= 1;
static float	F_TWO	= 2;
static float	F_FOUR	= 4;
static float	F_SIX	= 6;


/* Return largest of 3 values
 */
float max3(float a, float b, float c)
{    
    if (a > b)
	if (a > c)	return a;
	else		return c;
    else
	if (b > c)	return b;
	else		return c;
}


/* Return smallest of 3 values
 */
float min3(float a, float b, float c)
{
    if (a < b)
	if (a < c)	return a;
	else		return c;
    else
	if (b < c)	return b;
	else		return c;
}


/* Convert coordinates in RGB colour
 * space to coordinates in HSV colour space
 *    In:   r, g, b each in [0,1]
 *    Out:  h in [0, 1), s in (0, 1], and v in [0, 1]
 */
void rgb2hsv(float r,  float g,  float b,
             float *h, float *s, float *v)
{
    float	max = max3(r, g, b);
    float	min = min3(r, g, b);
    float	delta = max - min;

    *v = max;
    
    if (max != F_ZERO  &&  delta != F_ZERO) {

	*s = delta / max;
	if (r == max)
	    *h =          (g - b) / delta;	/* between yellow and magenta */
	else if (g == max)
	    *h = F_TWO  + (b - r) / delta;	/* between cyan and yellow */
	else
	    *h = F_FOUR + (r - g) / delta;	/* between magenta and cyan */
	
	*h /= F_SIX;
	while (*h < F_ZERO)
	    *h += F_ONE;

    } else {
	*s = F_ZERO;
	*h = F_ZERO;
    }
}


/* Convert coordinates in HSV colour space to
 * coordinates in RGB colour space
 *    In:  h in [0, 1), s in (0, 1], and v in [0, 1]
 *    Out: r, g, b each in [0,1]
 */
void hsv2rgb(float h,  float s,  float v,
	     float *r, float *g, float *b)
{
    float	f, p, q, t;
    
    if (s == F_ZERO) {
	*r = v;
	*g = v;
	*b = v;
	
    } else {
	while (h < F_ZERO)	    h += F_ONE;
	while (h >= F_ONE)	    h -= F_ONE;

	h *= F_SIX;
	f = h - (int) h;    /* fractional part of hue */
	
	p = v * (F_ONE - s);
	q = v * (F_ONE - (s * f));
	t = v * (F_ONE - (s * (F_ONE - f)));
	
	switch ((int) h) {
	  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;
	}
    }
}


/* YUV color space is a linear rearrangement of RGB.
 * Y is luminance, the black & white channel.
 */

static float Myuv2rgb[3][3] = {
    { 1.00000000,  0.00000000,  1.40200007 },
    { 1.00000000, -0.34413627, -0.71413624 },
    { 1.00000000,  1.77199996,  0.00000000 },
};

static float Mrgb2yuv[3][3] = {
    {  0.29900000,  0.58700000,  0.11400000 },
    { -0.16873589, -0.33126411,  0.50000000 },
    {  0.50000000, -0.41868759, -0.08131241 },
};


/* Rescale U and V so that they are [0,1] outside of rgb.c
 */
static float uBase  = -0.50;
static float vBase  = -0.50;


void TrimYUV(float y, float *u, float *v, float a, float b)
{
    /* Scale u and v, keeping y constant, so that color
     * falls within the RGB color unit cube.
     * To derive: start with the RGB = Myuv2rgb * YUV equations,
     *            fix Y, limit  0 <= RGB <= 1.
     *            Pick a new UV along a line from the old UV to UV = (0,0).
     *            UV = (0,0) is always a valid color (grey).
     */
    float	au  = a * *u;
    float	ybv = -y - b * *v;
    
    if (au < ybv) {
	if (*u == F_ZERO) {
	    *v = -y / b;
	} else {
	    float	vu = *v / *u;
	    *u = -y / (a + b * vu);
	    *v = vu * *u;
	}

    } else if (au > F_ONE + ybv) {
	if (*u == F_ZERO) {
	    *v = (F_ONE - y) / b;
	} else {
	    float	vu = *v / *u;
	    *u = (F_ONE - y) / (a + b * vu);
	    *v = vu * *u;
	}
    }
}


void yuv2rgb(float y,  float u,  float v,
	     float *r, float *g, float *b)
{
    u += uBase;
    v += vBase;
    
    TrimYUV(y, &u, &v, Myuv2rgb[0][1], Myuv2rgb[0][2]);
    TrimYUV(y, &u, &v, Myuv2rgb[1][1], Myuv2rgb[1][2]);
    TrimYUV(y, &u, &v, Myuv2rgb[2][1], Myuv2rgb[2][2]);

    *r = y + Myuv2rgb[0][1] * u + Myuv2rgb[0][2] * v;
    *g = y + Myuv2rgb[1][1] * u + Myuv2rgb[1][2] * v;
    *b = y + Myuv2rgb[2][1] * u + Myuv2rgb[2][2] * v;
}


void rgb2yuv(float r,  float g,  float b,
	     float *y, float *u, float *v)
{
    *y = Mrgb2yuv[0][0] * r + Mrgb2yuv[0][1] * g + Mrgb2yuv[0][2] * b;
    *u = Mrgb2yuv[1][0] * r + Mrgb2yuv[1][1] * g + Mrgb2yuv[1][2] * b;
    *v = Mrgb2yuv[2][0] * r + Mrgb2yuv[2][1] * g + Mrgb2yuv[2][2] * b;
    
    *u -= uBase;
    *v -= vBase;
}

