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 static:
28 	void toHLS(COLOR rgb, out HLSTYPE h, out HLSTYPE l, out HLSTYPE s)
29 	{
30 		auto R = rgb.r;
31 		auto G = rgb.g;
32 		auto B = rgb.b;
33 
34 		/* calculate lightness */
35 		auto cMax = max( max(R,G), B); /* max and min RGB values */
36 		auto cMin = min( min(R,G), B);
37 		l = ( ((cMax+cMin)*HLSMAX) + RGBMAX )/(2*RGBMAX);
38 
39 		if (cMax == cMin)              /* r=g=b --> achromatic case */
40 		{
41 			s = 0;                     /* saturation */
42 			h = UNDEFINED;             /* hue */
43 		}
44 		else                           /* chromatic case */
45 		{
46 			/* saturation */
47 			if (l <= (HLSMAX/2))
48 				s = cast(HLSTYPE)(( ((cMax-cMin)*HLSMAX) + ((cMax+cMin)/2) ) / (cMax+cMin));
49 			else
50 				s = cast(HLSTYPE)(( ((cMax-cMin)*HLSMAX) + ((2*RGBMAX-cMax-cMin)/2) )
51 				   / (2*RGBMAX-cMax-cMin));
52 
53 			/* hue */
54 			auto Rdelta = ( ((cMax-R)*(HLSMAX/6)) + ((cMax-cMin)/2) ) / (cMax-cMin); /* intermediate value: % of spread from max */
55 			auto Gdelta = ( ((cMax-G)*(HLSMAX/6)) + ((cMax-cMin)/2) ) / (cMax-cMin);
56 			auto Bdelta = ( ((cMax-B)*(HLSMAX/6)) + ((cMax-cMin)/2) ) / (cMax-cMin);
57 
58 			if (R == cMax)
59 				h = cast(HLSTYPE)(Bdelta - Gdelta);
60 			else if (G == cMax)
61 				h = cast(HLSTYPE)((HLSMAX/3) + Rdelta - Bdelta);
62 			else /* B == cMax */
63 				h = cast(HLSTYPE)(((2*HLSMAX)/3) + Gdelta - Rdelta);
64 
65 			if (h < 0)
66 				h += HLSMAX;
67 			if (h > HLSMAX)
68 				h -= HLSMAX;
69 		}
70 	}
71 
72 	/* utility routine for HLStoRGB */
73 	private HLSTYPE hueToRGB(HLSTYPE n1,HLSTYPE n2,HLSTYPE hue)
74 	{
75 		/* range check: note values passed add/subtract thirds of range */
76 		if (hue < 0)
77 			hue += HLSMAX;
78 
79 		if (hue > HLSMAX)
80 			hue -= HLSMAX;
81 
82 		/* return r,g, or b value from this tridrant */
83 		if (hue < (HLSMAX/6))
84 			return cast(HLSTYPE)( n1 + (((n2-n1)*hue+(HLSMAX/12))/(HLSMAX/6)) );
85 		if (hue < (HLSMAX/2))
86 			return cast(HLSTYPE)( n2 );
87 		if (hue < ((HLSMAX*2)/3))
88 			return cast(HLSTYPE)( n1 +    (((n2-n1)*(((HLSMAX*2)/3)-hue)+(HLSMAX/12))/(HLSMAX/6)));
89 		else
90 			return cast(HLSTYPE)( n1 );
91 	}
92 
93 	COLOR fromHLS(HLSTYPE hue, HLSTYPE lum, HLSTYPE sat)
94 	{
95 		COLOR c;
96 		HLSTYPE Magic1, Magic2;       /* calculated magic numbers (really!) */
97 
98 		if (sat == 0) {            /* achromatic case */
99 			c.r = c.g = c.b = cast(RGBTYPE)((lum*RGBMAX)/HLSMAX);
100 		//	assert(hue == UNDEFINED);
101 		}
102 		else  {                    /* chromatic case */
103 			/* set up magic numbers */
104 			if (lum <= (HLSMAX/2))
105 				Magic2 = cast(HLSTYPE)((lum*(HLSMAX + sat) + (HLSMAX/2))/HLSMAX);
106 			else
107 				Magic2 = cast(HLSTYPE)(lum + sat - ((lum*sat) + (HLSMAX/2))/HLSMAX);
108 			Magic1 = cast(HLSTYPE)(2*lum-Magic2);
109 
110 			/* get RGB, change units from HLSMAX to RGBMAX */
111 			c.r = cast(RGBTYPE)((hueToRGB(Magic1,Magic2,cast(HLSTYPE)(hue+(HLSMAX/3)))*RGBMAX + (HLSMAX/2))/HLSMAX);
112 			c.g = cast(RGBTYPE)((hueToRGB(Magic1,Magic2,cast(HLSTYPE)(hue           ))*RGBMAX + (HLSMAX/2))/HLSMAX);
113 			c.b = cast(RGBTYPE)((hueToRGB(Magic1,Magic2,cast(HLSTYPE)(hue-(HLSMAX/3)))*RGBMAX + (HLSMAX/2))/HLSMAX);
114 		}
115 		return c;
116 	}
117 }
118 
119 unittest
120 {
121 	import ae.utils.graphics.color;
122 	HLS!RGB hls;
123 	auto red = hls.fromHLS(0, 120, 240);
124 	assert(red == RGB(255, 0, 0));
125 	ushort h,l,s;
126 	hls.toHLS(red, h, l, s);
127 	assert(h==0 && l==120 && s==240);
128 }
129 
130 unittest
131 {
132 	import ae.utils.graphics.color;
133 	enum MAX = 30_000;
134 	HLS!(RGB, int, MAX) hls;
135 	auto red = hls.fromHLS(0, MAX/2, MAX);
136 	assert(red == RGB(255, 0, 0));
137 
138 	int h,l,s;
139 	hls.toHLS(red, h, l, s);
140 	assert(h==0 && l==MAX/2 && s==MAX);
141 }