delorie.com/archives/browse.cgi   search  
Mail Archives: djgpp/1997/06/17/19:37:33

Message-ID: <33A71FEA.BBD@xs4all.nl>
Date: Wed, 18 Jun 1997 01:38:19 +0200
From: "Andrei 'old-boy' Ellman" <ellman AT xs4all DOT nl>
Organization: Heslington Toast Parlour, Yorkshire.
MIME-Version: 1.0
To: djgpp AT delorie DOT com
CC: shawn AT talula DOT demon DOT co DOT uk
Subject: ALLEGRO: Replacement RGB2HSV functions and RGB2HLS functions

The hsv_to_rgb function that comes with Allegro has some bugs in it.
They must have crept in when converting from outputing RGB values in the
range [0,1] to the range [0,63]. Both the rgb_to_hsv and hsv_to_rgb
functions are poorly documented (ie. what are the valid ranges for the
input and output values).

Included in this message are replacement rgb<->hsv functions that work,
as well as rgb<->hls functions. All of these were copied from Foley &
VanDam's book on graphics (I've even attempted to correct a bug that was
in the book). The ranges are specified in the comments at the start of
each function.

For those that don't know what I'm talking about, HSV and HLS are
alternative methods of specifying a colour to RGB. RGB is what the
computer uses internally for specifying colours. The R,G,B values
represent the ammount of Red,Green,Blue in the colour. However, they are
not intuitive to visualise. HSV stands for Hue, Saturation, Value.
Imagine an upside-down cone with black at the bottom point, and on the
edge of the circle at the top, are all the colours of the spectrum. The
colours get darker as you go down towards the bottom point. As you
travel from the edge of the cone towards the middle (while retaining the
same height), the colours fade to whatever shade of grey is appropriate
for that height (white at the top, and black at the bottom, which is
just a single point). The Hue (H) is the position round the circle. The
Value (V) is the height of the position in the cone. The Saturation (S)
is the relative distance from the edge of the cone to the center (at the
same height) ranging from 0-1. HSV is also sometimes known as HSB
(B=Brightness). The HLS model is similar, but the white-point which is
the top of the upside-down cone has been pulled-up to form a
double-cone. This means that the most colourful colours have an L of 0.5
(where the two cones are joined together)(they had as V of 1.0 at the
top of the single cone), and as you increase l, all colours tend to
white. The bottom of the double-cone is still black.

Anyway, here is the code (you can even use it without ALLEGRO):

----------------------------------------------------------------------

/*
Anyone not using ALLEGRO should un-vomment this typedef
and the definitions below
*/
/*
typedef struct RGB
{
   unsigned char r, g, b;
} RGB;
#ifndef MIN
#define MIN(x,y)     (((x) < (y)) ? (x) : (y))
#endif
#ifndef MAX
#define MAX(x,y)     (((x) > (y)) ? (x) : (y))
#endif
*/

#define MAX_INTENSITY 63.0
#define UNDEFINED -999.0

/* hsv_to_rgb:
 *  Converts from HSV colorspace to RGB values.
 *  Taken From Foley & VanDam Fig. 13.34 with a few modifications.
 *  Input: h[0.0,6.0 or UNDEFINED] s[0.0,1.0] v[0.0,1.0]
 *  (s must be 0.0 when h is UNDEFINED)
 *  Output: An RGB structure. each RGB in [0,MAX_INTENSITY]
 *  Note: This does not check paramaters for the valid ranges,
 *  but h=6.0 becomes h=0.0
 */
void hsv_to_rgb (float h, float s, float v, RGB *rgb)
{
   float i, f, p, q, t;

   if (s == 0.0) {   /* greyscale (h should ideally be UNDEFINED) */
      rgb->r = rgb->g = rgb->b = (int)(v * MAX_INTENSITY);
   }
   else {
      if (h == 6.0)
	 h = 0.0;	/* Hue is a cyclic paramater measued from 0-6 */

      i = floor(h);	/* i is largest integer <= h */
      f = h - i;	/* f is fractional part of h */
      p = (v * (1.0 - s) * MAX_INTENSITY);
      q = (v * (1.0 - (s * f)) * MAX_INTENSITY);
      t = (v * (1.0 - (s * (1.0 - f))) * MAX_INTENSITY);

      switch ((int)i) {
	 case 0:
	 	rgb->r = (unsigned char)(v * MAX_INTENSITY);
	 	rgb->g = (unsigned char)(t * MAX_INTENSITY);
		rgb->b = (unsigned char)(p * MAX_INTENSITY);
		break;
	 case 1:
	 	rgb->r = (unsigned char)(q * MAX_INTENSITY);
		rgb->g = (unsigned char)(v * MAX_INTENSITY);
		rgb->b = (unsigned char)(p * MAX_INTENSITY);
		break;
	 case 2:
	 	rgb->r = (unsigned char)(p * MAX_INTENSITY);
		rgb->g = (unsigned char)(v * MAX_INTENSITY);
		rgb->b = (unsigned char)(t * MAX_INTENSITY);
		break;
	 case 3:
	 	rgb->r = (unsigned char)(p * MAX_INTENSITY);
		rgb->g = (unsigned char)(q * MAX_INTENSITY);
		rgb->b = (unsigned char)(v * MAX_INTENSITY);
		break;
	 case 4:
	 	rgb->r = (unsigned char)(t * MAX_INTENSITY);
		rgb->g = (unsigned char)(p * MAX_INTENSITY);
		rgb->b = (unsigned char)(v * MAX_INTENSITY);
		break;
	 case 5:
	 	rgb->r = (unsigned char)(t * MAX_INTENSITY);
		rgb->g = (unsigned char)(p * MAX_INTENSITY);
		rgb->b = (unsigned char)(q * MAX_INTENSITY);
		break;
      }
   }
}	/* End of hsv_to_rgb */


/* rgb_to_hsv:
 *  Converts an RGB value into the HSV colorspace.
 *  Taken From Foley & VanDam Fig. 13.33 with a few modifications.
 *  Input: An RGB structure. each RGB in [0,MAX_INTENSITY]
 *  Output: h[0.0,6.0] s[0.0,1.0] v[0.0,1.0]
 * 	h is UNDEFINED when s=0.0
 *  Note: This does not check paramaters for the valid ranges,
 */
void rgb_to_hsv(RGB *rgb, float *h, float *s, float *v)
{
   float min, max, delta, r, g, b;

   r = (float)(rgb->r) / MAX_INTENSITY;
   g = (float)(rgb->g) / MAX_INTENSITY;
   b = (float)(rgb->b) / MAX_INTENSITY;

   max = MAX(r, MAX(g, b));
   min = MIN(r, MIN(g, b));

   *v = max;	/* The V component is the maximum of r,g,b */

   delta = max - min;

   /* Calculate Saturation: 0.0 if r,g,b all 0 */
   *s = (max != 0.0) ? (delta / max) : 0.0;

   if (*s == 0.0)
      *h = UNDEFINED;        /* colour has no hue */
   else {
      if (r == max)
	 *h = (g-b) / delta;  /* Resulting colour between yellow & magenta */
      else if (g == max)
	 *h = 2.0 + (b-r) / delta;  /* Resulting colour between cyan & yellow
*/
      else if (b == max)
	 *h = 4.0 + (r-g) / delta;  /* Resulting colour between magenta & cyan
*/
      if (*h < 0.0)
	 *h += 6.0;	/* Make sure hue is non-negative */
    }
}	/* End of rgb_to_hsv */


/* hls_to_rgb:
 *  Converts from HLS colorspace to RGB values.
 *  Taken From Foley & VanDam Fig. 13.37 with a few modifications.
 *  Input: h[0.0,6.0 or UNDEFINED] s[0.0,1.0] v[0.0,1.0]
 *  (s must be 0.0 when h is UNDEFINED)
 *  Output: An RGB structure. each RGB in [0,MAX_INTENSITY]
 *  Note: This does not check paramaters for the valid ranges,
 */

/* First, a helper function for working out a value for a colour chanel
*/
float hls_to_rgb_value (float n1, float n2, float hue)
{
	if (hue > 6.0)
		hue -= 6.0;
	else if (hue < 0.0)
		hue += 6.0;

	if (hue < 1.0)
		return n1 + (n2-n1)*hue;
	else if (hue < 3.0)
		return n2;
	else if (hue < 4.0)
		return n1 + (n2-n1)*(4.0-hue);
	else
		return n1;
}

/* The function proper */
void hls_to_rgb (float h, float l, float s, RGB *rgb)
{
	float m1, m2;

	/*
	AE: The commented out line appeared in Foley&VanDam
	The line below is it my attempt to correct it. I think it works.
	*/
	/* m2 = (l<=0.5) ? (l*(l+s)) : (l+s - (l*s)); */
	m2 = (l<=0.5) ? (l+(l*s)) : (l+s - (l*s));
	m1 = 2.0 * l - m2;

	if (s == 0.0) {   /* greyscale (h should ideally be UNDEFINED) */
    	rgb->r = rgb->g = rgb->b = (unsigned char)(l * MAX_INTENSITY);
	} else {
		rgb->r = (unsigned char)(hls_to_rgb_value(m1, m2, h+2.0) *
MAX_INTENSITY);
		rgb->g = (unsigned char)(hls_to_rgb_value(m1, m2, h) * MAX_INTENSITY);
		rgb->b = (unsigned char)(hls_to_rgb_value(m1, m2, h-2.0) *
MAX_INTENSITY);
	}
}	/* End of hls_to_rgb */


/* rgb_to_hls:
 *  Converts an RGB value into the HLS colorspace.
 *  Taken From Foley & VanDam Fig. 13.36 with a few modifications.
 *  Input: An RGB structure. each RGB in [0,MAX_INTENSITY]
 *  Output: h[0.0,6.0] s[0.0,1.0] v[0.0,1.0]
 * 	h is UNDEFINED when s=0.0
 *  Note: This does not check paramaters for the valid ranges,
 */
void rgb_to_hls(RGB *rgb, float *h, float *l, float *s)
{
   float min, max, delta, r, g, b;

   r = (float)(rgb->r) / MAX_INTENSITY;
   g = (float)(rgb->g) / MAX_INTENSITY;
   b = (float)(rgb->b) / MAX_INTENSITY;

   max = MAX(r, MAX(g, b));
   min = MIN(r, MIN(g, b));

   *l = (max+min)/2.0;	/* L is the average of maximum and minimum r,g,b
*/

   delta = max - min;

   if (delta == 0.0) {
	   *s = 0.0;
	   *h = UNDEFINED;        /* colour has no hue */

   } else {

      /* Calculate Saturation */
      *s = (*l<=0.5) ? (delta/(max+min)) : (delta/(2.0-(max+min)));

      /* Calculate Hue */
      if (r == max)
	 *h = (g-b) / delta;  /* Resulting colour between yellow & magenta */
      else if (g == max)
	 *h = 2.0 + (b-r) / delta;  /* Resulting colour between cyan & yellow
*/
      else if (b == max)
	 *h = 4.0 + (r-g) / delta;  /* Resulting colour between magenta & cyan
*/
      if (*h < 0.0)
	 *h += 6.0;	/* Make sure hue is non-negative */
    }
}	/* End of rgb_to_hls */



-- 
Andrei Ellman - URL: http://www.xs4all.nl/~ellman/ae-a - ae1 AT york DOT ac DOT uk
"All I wanna do is have some fun     :-)     || ae-a AT minster DOT york DOT ac DOT uk
 I've got the feeling I'm not the only one"  || mailto:ellman AT xs4all DOT nl
     -- Sheryl Crow      :-)    ||       It's what you make of it.

- Raw text -


  webmaster     delorie software   privacy  
  Copyright © 2019   by DJ Delorie     Updated Jul 2019