1 module ae.utils.graphics.hls;
2 
3 import ae.utils.math;
4 
5 /// RGB <-> HLS conversion
6 /// based on http://support.microsoft.com/kb/29240
7 
8 struct HLS(COLOR, HLSTYPE=ushort, HLSTYPE HLSMAX=240)
9 {
10 	static assert(HLSMAX <= ushort.max, "TODO");
11 
12 	// H,L, and S vary over 0-HLSMAX
13 	// HLSMAX BEST IF DIVISIBLE BY 6
14 
15 	alias COLOR.ChannelType RGBTYPE;
16 
17 	// R,G, and B vary over 0-RGBMAX
18 	enum RGBMAX = RGBTYPE.max;
19 
20 	// Hue is undefined if Saturation is 0 (grey-scale)
21 	// This value determines where the Hue scrollbar is
22 	// initially set for achromatic colors
23 	enum UNDEFINED = HLSMAX*2/3;
24 
25 	void toHLS(COLOR rgb, out HLSTYPE h, out HLSTYPE l, out HLSTYPE s)
26 	{
27 		auto R = rgb.r;
28 		auto G = rgb.g;
29 		auto B = rgb.b;
30 
31 		/* calculate lightness */
32 		auto cMax = max( max(R,G), B); /* max and min RGB values */
33 		auto cMin = min( min(R,G), B);
34 		l = ( ((cMax+cMin)*HLSMAX) + RGBMAX )/(2*RGBMAX);
35 
36 		if (cMax == cMin)              /* r=g=b --> achromatic case */
37 		{
38 			s = 0;                     /* saturation */
39 			h = UNDEFINED;             /* hue */
40 		}
41 		else                           /* chromatic case */
42 		{
43 			/* saturation */
44 			if (l <= (HLSMAX/2))
45 				s = cast(HLSTYPE)(( ((cMax-cMin)*HLSMAX) + ((cMax+cMin)/2) ) / (cMax+cMin));
46 			else
47 				s = cast(HLSTYPE)(( ((cMax-cMin)*HLSMAX) + ((2*RGBMAX-cMax-cMin)/2) )
48 				   / (2*RGBMAX-cMax-cMin));
49 
50 			/* hue */
51 			auto Rdelta = ( ((cMax-R)*(HLSMAX/6)) + ((cMax-cMin)/2) ) / (cMax-cMin); /* intermediate value: % of spread from max */
52 			auto Gdelta = ( ((cMax-G)*(HLSMAX/6)) + ((cMax-cMin)/2) ) / (cMax-cMin);
53 			auto Bdelta = ( ((cMax-B)*(HLSMAX/6)) + ((cMax-cMin)/2) ) / (cMax-cMin);
54 
55 			if (R == cMax)
56 				h = cast(HLSTYPE)(Bdelta - Gdelta);
57 			else if (G == cMax)
58 				h = cast(HLSTYPE)((HLSMAX/3) + Rdelta - Bdelta);
59 			else /* B == cMax */
60 				h = cast(HLSTYPE)(((2*HLSMAX)/3) + Gdelta - Rdelta);
61 
62 			if (h < 0)
63 				h += HLSMAX;
64 			if (h > HLSMAX)
65 				h -= HLSMAX;
66 		}
67 	}
68 
69 	/* utility routine for HLStoRGB */
70 	private HLSTYPE hueToRGB(HLSTYPE n1,HLSTYPE n2,HLSTYPE hue)
71 	{
72 		/* range check: note values passed add/subtract thirds of range */
73 		if (hue < 0)
74 			hue += HLSMAX;
75 
76 		if (hue > HLSMAX)
77 			hue -= HLSMAX;
78 
79 		/* return r,g, or b value from this tridrant */
80 		if (hue < (HLSMAX/6))
81 			return cast(HLSTYPE)( n1 + (((n2-n1)*hue+(HLSMAX/12))/(HLSMAX/6)) );
82 		if (hue < (HLSMAX/2))
83 			return cast(HLSTYPE)( n2 );
84 		if (hue < ((HLSMAX*2)/3))
85 			return cast(HLSTYPE)( n1 +    (((n2-n1)*(((HLSMAX*2)/3)-hue)+(HLSMAX/12))/(HLSMAX/6)));
86 		else
87 			return cast(HLSTYPE)( n1 );
88 	}
89 
90 	COLOR fromHLS(HLSTYPE hue, HLSTYPE lum, HLSTYPE sat)
91 	{
92 		COLOR c;
93 		HLSTYPE Magic1, Magic2;       /* calculated magic numbers (really!) */
94 
95 		if (sat == 0) {            /* achromatic case */
96 			c.r = c.g = c.b = cast(RGBTYPE)((lum*RGBMAX)/HLSMAX);
97 		//	assert(hue == UNDEFINED);
98 		}
99 		else  {                    /* chromatic case */
100 			/* set up magic numbers */
101 			if (lum <= (HLSMAX/2))
102 				Magic2 = cast(HLSTYPE)((lum*(HLSMAX + sat) + (HLSMAX/2))/HLSMAX);
103 			else
104 				Magic2 = cast(HLSTYPE)(lum + sat - ((lum*sat) + (HLSMAX/2))/HLSMAX);
105 			Magic1 = cast(HLSTYPE)(2*lum-Magic2);
106 
107 			/* get RGB, change units from HLSMAX to RGBMAX */
108 			c.r = cast(RGBTYPE)((hueToRGB(Magic1,Magic2,cast(HLSTYPE)(hue+(HLSMAX/3)))*RGBMAX + (HLSMAX/2))/HLSMAX);
109 			c.g = cast(RGBTYPE)((hueToRGB(Magic1,Magic2,cast(HLSTYPE)(hue           ))*RGBMAX + (HLSMAX/2))/HLSMAX);
110 			c.b = cast(RGBTYPE)((hueToRGB(Magic1,Magic2,cast(HLSTYPE)(hue-(HLSMAX/3)))*RGBMAX + (HLSMAX/2))/HLSMAX);
111 		}
112 		return c;
113 	}
114 }
115 
116 unittest
117 {
118 	import ae.utils.graphics.color;
119 	HLS!RGB hls;
120 	auto red = hls.fromHLS(0, 120, 240);
121 	assert(red == RGB(255, 0, 0));
122 	ushort h,l,s;
123 	hls.toHLS(red, h, l, s);
124 	assert(h==0 && l==120 && s==240);
125 }
126 
127 unittest
128 {
129 	import ae.utils.graphics.color;
130 	enum MAX = 30_000;
131 	HLS!(RGB, int, MAX) hls;
132 	auto red = hls.fromHLS(0, MAX/2, MAX);
133 	assert(red == RGB(255, 0, 0));
134 
135 	int h,l,s;
136 	hls.toHLS(red, h, l, s);
137 	assert(h==0 && l==MAX/2 && s==MAX);
138 }