Message-ID: <33A71FEA.BBD@xs4all.nl> Date: Wed, 18 Jun 1997 01:38:19 +0200 From: "Andrei 'old-boy' Ellman" 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 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit Precedence: bulk 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.