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