1 module ae.utils.graphics.hls; 2 3 import ae.utils.math; 4 5 /// RGB <-> HLS conversion 6 /// based on http://support.microsoft.com/kb/29240 7 8 struct HLS(COLOR, HLSTYPE=ushort, HLSTYPE HLSMAX=240) 9 { 10 static assert(HLSMAX <= ushort.max, "TODO"); 11 12 // H,L, and S vary over 0-HLSMAX 13 // HLSMAX BEST IF DIVISIBLE BY 6 14 15 alias COLOR.ChannelType RGBTYPE; 16 17 // R,G, and B vary over 0-RGBMAX 18 enum RGBMAX = RGBTYPE.max; 19 20 // Hue is undefined if Saturation is 0 (grey-scale) 21 // This value determines where the Hue scrollbar is 22 // initially set for achromatic colors 23 enum UNDEFINED = HLSMAX*2/3; 24 25 void toHLS(COLOR rgb, out HLSTYPE h, out HLSTYPE l, out HLSTYPE s) 26 { 27 auto R = rgb.r; 28 auto G = rgb.g; 29 auto B = rgb.b; 30 31 /* calculate lightness */ 32 auto cMax = max( max(R,G), B); /* max and min RGB values */ 33 auto cMin = min( min(R,G), B); 34 l = ( ((cMax+cMin)*HLSMAX) + RGBMAX )/(2*RGBMAX); 35 36 if (cMax == cMin) /* r=g=b --> achromatic case */ 37 { 38 s = 0; /* saturation */ 39 h = UNDEFINED; /* hue */ 40 } 41 else /* chromatic case */ 42 { 43 /* saturation */ 44 if (l <= (HLSMAX/2)) 45 s = cast(HLSTYPE)(( ((cMax-cMin)*HLSMAX) + ((cMax+cMin)/2) ) / (cMax+cMin)); 46 else 47 s = cast(HLSTYPE)(( ((cMax-cMin)*HLSMAX) + ((2*RGBMAX-cMax-cMin)/2) ) 48 / (2*RGBMAX-cMax-cMin)); 49 50 /* hue */ 51 auto Rdelta = ( ((cMax-R)*(HLSMAX/6)) + ((cMax-cMin)/2) ) / (cMax-cMin); /* intermediate value: % of spread from max */ 52 auto Gdelta = ( ((cMax-G)*(HLSMAX/6)) + ((cMax-cMin)/2) ) / (cMax-cMin); 53 auto Bdelta = ( ((cMax-B)*(HLSMAX/6)) + ((cMax-cMin)/2) ) / (cMax-cMin); 54 55 if (R == cMax) 56 h = cast(HLSTYPE)(Bdelta - Gdelta); 57 else if (G == cMax) 58 h = cast(HLSTYPE)((HLSMAX/3) + Rdelta - Bdelta); 59 else /* B == cMax */ 60 h = cast(HLSTYPE)(((2*HLSMAX)/3) + Gdelta - Rdelta); 61 62 if (h < 0) 63 h += HLSMAX; 64 if (h > HLSMAX) 65 h -= HLSMAX; 66 } 67 } 68 69 /* utility routine for HLStoRGB */ 70 private HLSTYPE hueToRGB(HLSTYPE n1,HLSTYPE n2,HLSTYPE hue) 71 { 72 /* range check: note values passed add/subtract thirds of range */ 73 if (hue < 0) 74 hue += HLSMAX; 75 76 if (hue > HLSMAX) 77 hue -= HLSMAX; 78 79 /* return r,g, or b value from this tridrant */ 80 if (hue < (HLSMAX/6)) 81 return cast(HLSTYPE)( n1 + (((n2-n1)*hue+(HLSMAX/12))/(HLSMAX/6)) ); 82 if (hue < (HLSMAX/2)) 83 return cast(HLSTYPE)( n2 ); 84 if (hue < ((HLSMAX*2)/3)) 85 return cast(HLSTYPE)( n1 + (((n2-n1)*(((HLSMAX*2)/3)-hue)+(HLSMAX/12))/(HLSMAX/6))); 86 else 87 return cast(HLSTYPE)( n1 ); 88 } 89 90 COLOR fromHLS(HLSTYPE hue, HLSTYPE lum, HLSTYPE sat) 91 { 92 COLOR c; 93 HLSTYPE Magic1, Magic2; /* calculated magic numbers (really!) */ 94 95 if (sat == 0) { /* achromatic case */ 96 c.r = c.g = c.b = cast(RGBTYPE)((lum*RGBMAX)/HLSMAX); 97 // assert(hue == UNDEFINED); 98 } 99 else { /* chromatic case */ 100 /* set up magic numbers */ 101 if (lum <= (HLSMAX/2)) 102 Magic2 = cast(HLSTYPE)((lum*(HLSMAX + sat) + (HLSMAX/2))/HLSMAX); 103 else 104 Magic2 = cast(HLSTYPE)(lum + sat - ((lum*sat) + (HLSMAX/2))/HLSMAX); 105 Magic1 = cast(HLSTYPE)(2*lum-Magic2); 106 107 /* get RGB, change units from HLSMAX to RGBMAX */ 108 c.r = cast(RGBTYPE)((hueToRGB(Magic1,Magic2,cast(HLSTYPE)(hue+(HLSMAX/3)))*RGBMAX + (HLSMAX/2))/HLSMAX); 109 c.g = cast(RGBTYPE)((hueToRGB(Magic1,Magic2,cast(HLSTYPE)(hue ))*RGBMAX + (HLSMAX/2))/HLSMAX); 110 c.b = cast(RGBTYPE)((hueToRGB(Magic1,Magic2,cast(HLSTYPE)(hue-(HLSMAX/3)))*RGBMAX + (HLSMAX/2))/HLSMAX); 111 } 112 return c; 113 } 114 } 115 116 unittest 117 { 118 import ae.utils.graphics.color; 119 HLS!RGB hls; 120 auto red = hls.fromHLS(0, 120, 240); 121 assert(red == RGB(255, 0, 0)); 122 ushort h,l,s; 123 hls.toHLS(red, h, l, s); 124 assert(h==0 && l==120 && s==240); 125 } 126 127 unittest 128 { 129 import ae.utils.graphics.color; 130 enum MAX = 30_000; 131 HLS!(RGB, int, MAX) hls; 132 auto red = hls.fromHLS(0, MAX/2, MAX); 133 assert(red == RGB(255, 0, 0)); 134 135 int h,l,s; 136 hls.toHLS(red, h, l, s); 137 assert(h==0 && l==MAX/2 && s==MAX); 138 }