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