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 }