1 module arsd.color; 2 3 // NOTE: this is obsolete. use color.d instead. 4 5 import std.stdio; 6 import std.math; 7 import std.conv; 8 import std.algorithm; 9 10 struct Color { 11 ubyte r; 12 ubyte g; 13 ubyte b; 14 ubyte a; 15 16 this(int red, int green, int blue, int alpha = 255) { 17 this.r = cast(ubyte) red; 18 this.g = cast(ubyte) green; 19 this.b = cast(ubyte) blue; 20 this.a = cast(ubyte) alpha; 21 } 22 23 static Color transparent() { 24 return Color(0, 0, 0, 0); 25 } 26 27 static Color white() { 28 return Color(255, 255, 255); 29 } 30 31 static Color black() { 32 return Color(0, 0, 0); 33 } 34 35 string toString() { 36 import std.string; 37 if(a == 255) 38 return format("%02x%02x%02x", r, g, b); 39 else 40 return format("%02x%02x%02x%02x", r, g, b, a); 41 } 42 } 43 44 45 Color fromHsl(real[3] hsl) { 46 return fromHsl(hsl[0], hsl[1], hsl[2]); 47 } 48 49 Color fromHsl(real h, real s, real l) { 50 h = h % 360; 51 52 real C = (1 - abs(2 * l - 1)) * s; 53 54 real hPrime = h / 60; 55 56 real X = C * (1 - abs(hPrime % 2 - 1)); 57 58 real r, g, b; 59 60 if(h is real.nan) 61 r = g = b = 0; 62 else if (hPrime >= 0 && hPrime < 1) { 63 r = C; 64 g = X; 65 b = 0; 66 } else if (hPrime >= 1 && hPrime < 2) { 67 r = X; 68 g = C; 69 b = 0; 70 } else if (hPrime >= 2 && hPrime < 3) { 71 r = 0; 72 g = C; 73 b = X; 74 } else if (hPrime >= 3 && hPrime < 4) { 75 r = 0; 76 g = X; 77 b = C; 78 } else if (hPrime >= 4 && hPrime < 5) { 79 r = X; 80 g = 0; 81 b = C; 82 } else if (hPrime >= 5 && hPrime < 6) { 83 r = C; 84 g = 0; 85 b = X; 86 } 87 88 real m = l - C / 2; 89 90 r += m; 91 g += m; 92 b += m; 93 94 return Color( 95 cast(ubyte)(r * 255), 96 cast(ubyte)(g * 255), 97 cast(ubyte)(b * 255), 98 255); 99 } 100 101 real[3] toHsl(Color c, bool useWeightedLightness = false) { 102 real r1 = cast(real) c.r / 255; 103 real g1 = cast(real) c.g / 255; 104 real b1 = cast(real) c.b / 255; 105 106 real maxColor = max(r1, g1, b1); 107 real minColor = min(r1, g1, b1); 108 109 real L = (maxColor + minColor) / 2 ; 110 if(useWeightedLightness) { 111 // the colors don't affect the eye equally 112 // this is a little more accurate than plain HSL numbers 113 L = 0.2126*r1 + 0.7152*g1 + 0.0722*b1; 114 } 115 real S = 0; 116 real H = 0; 117 if(maxColor != minColor) { 118 if(L < 0.5) { 119 S = (maxColor - minColor) / (maxColor + minColor); 120 } else { 121 S = (maxColor - minColor) / (2.0 - maxColor - minColor); 122 } 123 if(r1 == maxColor) { 124 H = (g1-b1) / (maxColor - minColor); 125 } else if(g1 == maxColor) { 126 H = 2.0 + (b1 - r1) / (maxColor - minColor); 127 } else { 128 H = 4.0 + (r1 - g1) / (maxColor - minColor); 129 } 130 } 131 132 H = H * 60; 133 if(H < 0){ 134 H += 360; 135 } 136 137 return [H, S, L]; 138 } 139 140 141 Color lighten(Color c, real percentage) { 142 auto hsl = toHsl(c); 143 hsl[2] *= (1 + percentage); 144 if(hsl[2] > 1) 145 hsl[2] = 1; 146 return fromHsl(hsl); 147 } 148 149 Color darken(Color c, real percentage) { 150 auto hsl = toHsl(c); 151 hsl[2] *= (1 - percentage); 152 return fromHsl(hsl); 153 } 154 155 /// for light colors, call darken. for dark colors, call lighten. 156 /// The goal: get toward center grey. 157 Color moderate(Color c, real percentage) { 158 auto hsl = toHsl(c); 159 if(hsl[2] > 0.5) 160 hsl[2] *= (1 - percentage); 161 else { 162 if(hsl[2] <= 0.01) // if we are given black, moderating it means getting *something* out 163 hsl[2] = percentage; 164 else 165 hsl[2] *= (1 + percentage); 166 } 167 if(hsl[2] > 1) 168 hsl[2] = 1; 169 return fromHsl(hsl); 170 } 171 172 /// the opposite of moderate. Make darks darker and lights lighter 173 Color extremify(Color c, real percentage) { 174 auto hsl = toHsl(c, true); 175 if(hsl[2] < 0.5) 176 hsl[2] *= (1 - percentage); 177 else 178 hsl[2] *= (1 + percentage); 179 if(hsl[2] > 1) 180 hsl[2] = 1; 181 return fromHsl(hsl); 182 } 183 184 /// Move around the lightness wheel, trying not to break on moderate things 185 Color oppositeLightness(Color c) { 186 auto hsl = toHsl(c); 187 188 auto original = hsl[2]; 189 190 if(original > 0.4 && original < 0.6) 191 hsl[2] = 0.8 - original; // so it isn't quite the same 192 else 193 hsl[2] = 1 - original; 194 195 return fromHsl(hsl); 196 } 197 198 /// Try to determine a text color - either white or black - based on the input 199 Color makeTextColor(Color c) { 200 auto hsl = toHsl(c, true); // give green a bonus for contrast 201 if(hsl[2] > 0.5) 202 return Color(0, 0, 0); 203 else 204 return Color(255, 255, 255); 205 } 206 207 Color setLightness(Color c, real lightness) { 208 auto hsl = toHsl(c); 209 hsl[2] = lightness; 210 return fromHsl(hsl); 211 } 212 213 214 215 Color rotateHue(Color c, real degrees) { 216 auto hsl = toHsl(c); 217 hsl[0] += degrees; 218 return fromHsl(hsl); 219 } 220 221 Color setHue(Color c, real hue) { 222 auto hsl = toHsl(c); 223 hsl[0] = hue; 224 return fromHsl(hsl); 225 } 226 227 Color desaturate(Color c, real percentage) { 228 auto hsl = toHsl(c); 229 hsl[1] *= (1 - percentage); 230 return fromHsl(hsl); 231 } 232 233 Color saturate(Color c, real percentage) { 234 auto hsl = toHsl(c); 235 hsl[1] *= (1 + percentage); 236 if(hsl[1] > 1) 237 hsl[1] = 1; 238 return fromHsl(hsl); 239 } 240 241 Color setSaturation(Color c, real saturation) { 242 auto hsl = toHsl(c); 243 hsl[1] = saturation; 244 return fromHsl(hsl); 245 } 246 247 248 /* 249 void main(string[] args) { 250 auto color1 = toHsl(Color(255, 0, 0)); 251 auto color = fromHsl(color1[0] + 60, color1[1], color1[2]); 252 253 writefln("#%02x%02x%02x", color.r, color.g, color.b); 254 } 255 */ 256 257 /* Color algebra functions */ 258 259 /* Alpha putpixel looks like this: 260 261 void putPixel(Image i, Color c) { 262 Color b; 263 b.r = i.data[(y * i.width + x) * bpp + 0]; 264 b.g = i.data[(y * i.width + x) * bpp + 1]; 265 b.b = i.data[(y * i.width + x) * bpp + 2]; 266 b.a = i.data[(y * i.width + x) * bpp + 3]; 267 268 float ca = cast(float) c.a / 255; 269 270 i.data[(y * i.width + x) * bpp + 0] = alpha(c.r, ca, b.r); 271 i.data[(y * i.width + x) * bpp + 1] = alpha(c.g, ca, b.g); 272 i.data[(y * i.width + x) * bpp + 2] = alpha(c.b, ca, b.b); 273 i.data[(y * i.width + x) * bpp + 3] = alpha(c.a, ca, b.a); 274 } 275 276 ubyte alpha(ubyte c1, float alpha, ubyte onto) { 277 auto got = (1 - alpha) * onto + alpha * c1; 278 279 if(got > 255) 280 return 255; 281 return cast(ubyte) got; 282 } 283 284 So, given the background color and the resultant color, what was 285 composited on to it? 286 */ 287 288 ubyte unalpha(ubyte colorYouHave, float alpha, ubyte backgroundColor) { 289 // resultingColor = (1-alpha) * backgroundColor + alpha * answer 290 auto resultingColorf = cast(float) colorYouHave; 291 auto backgroundColorf = cast(float) backgroundColor; 292 293 auto answer = (resultingColorf - backgroundColorf + alpha * backgroundColorf) / alpha; 294 if(answer > 255) 295 return 255; 296 if(answer < 0) 297 return 0; 298 return cast(ubyte) answer; 299 } 300 301 ubyte makeAlpha(ubyte colorYouHave, ubyte backgroundColor/*, ubyte foreground = 0x00*/) { 302 //auto foregroundf = cast(float) foreground; 303 auto foregroundf = 0.00f; 304 auto colorYouHavef = cast(float) colorYouHave; 305 auto backgroundColorf = cast(float) backgroundColor; 306 307 // colorYouHave = backgroundColorf - alpha * backgroundColorf + alpha * foregroundf 308 auto alphaf = 1 - colorYouHave / backgroundColorf; 309 alphaf *= 255; 310 311 if(alphaf < 0) 312 return 0; 313 if(alphaf > 255) 314 return 255; 315 return cast(ubyte) alphaf; 316 } 317 318 319 int fromHex(string s) { 320 import std.range; 321 int result = 0; 322 323 int exp = 1; 324 foreach(c; retro(s)) { 325 if(c >= 'A' && c <= 'F') 326 result += exp * (c - 'A' + 10); 327 else if(c >= 'a' && c <= 'f') 328 result += exp * (c - 'a' + 10); 329 else if(c >= '0' && c <= '9') 330 result += exp * (c - '0'); 331 else 332 throw new Exception("invalid hex character: " ~ cast(char) c); 333 334 exp *= 16; 335 } 336 337 return result; 338 } 339 340 Color colorFromString(string s) { 341 if(s.length == 0) 342 return Color(0,0,0,255); 343 if(s[0] == '#') 344 s = s[1..$]; 345 assert(s.length == 6 || s.length == 8); 346 347 Color c; 348 349 c.r = cast(ubyte) fromHex(s[0..2]); 350 c.g = cast(ubyte) fromHex(s[2..4]); 351 c.b = cast(ubyte) fromHex(s[4..6]); 352 if(s.length == 8) 353 c.a = cast(ubyte) fromHex(s[6..8]); 354 else 355 c.a = 255; 356 357 return c; 358 }