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 }