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 }