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 }