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 }