1 ///
2 module arsd.color;
3 
4 @safe:
5 
6 // importing phobos explodes the size of this code 10x, so not doing it.
7 
8 private {
9 	real toInternal(T)(string s) {
10 		real accumulator = 0.0;
11 		size_t i = s.length;
12 		foreach(idx, c; s) {
13 			if(c >= '0' && c <= '9') {
14 				accumulator *= 10;
15 				accumulator += c - '0';
16 			} else if(c == '.') {
17 				i = idx + 1;
18 				break;
19 			} else
20 				throw new Exception("bad char to make real from " ~ s);
21 		}
22 		
23 		real accumulator2 = 0.0;
24 		real count = 1;
25 		foreach(c; s[i .. $]) {
26 			if(c >= '0' && c <= '9') {
27 				accumulator2 *= 10;
28 				accumulator2 += c - '0';
29 				count *= 10;
30 			} else
31 				throw new Exception("bad char to make real from " ~ s);
32 		}
33 		
34 		return accumulator + accumulator2 / count;
35 	}
36 	
37 	@trusted
38 	string toInternal(T)(int a) {
39 		if(a == 0)
40 			return "0";
41 		char[] ret;
42 		while(a) {
43 			ret ~= (a % 10) + '0';
44 			a /= 10;
45 		}
46 		for(int i = 0; i < ret.length / 2; i++) {
47 			char c = ret[i];
48 			ret[i] = ret[$ - i - 1];
49 			ret[$ - i - 1] = c;
50 		}
51 		return cast(string) ret;
52 	}
53 	string toInternal(T)(real a) {
54 		// a simplifying assumption here is the fact that we only use this in one place: toInternal!string(cast(real) a / 255)
55 		// thus we know this will always be between 0.0 and 1.0, inclusive.
56 		if(a <= 0.0)
57 			return "0.0";
58 		if(a >= 1.0)
59 			return "1.0";
60 		string ret = "0.";
61 		// I wonder if I can handle round off error any better. Phobos does, but that isn't worth 100 KB of code.
62 		int amt = cast(int)(a * 1000);
63 		return ret ~ toInternal!string(amt);
64 	}
65 	
66 	nothrow @safe @nogc pure
67 	real absInternal(real a) { return a < 0 ? -a : a; }
68 	nothrow @safe @nogc pure
69 	real minInternal(real a, real b, real c) {
70 		auto m = a;
71 		if(b < m) m = b;
72 		if(c < m) m = c;
73 		return m;
74 	}
75 	nothrow @safe @nogc pure
76 	real maxInternal(real a, real b, real c) {
77 		auto m = a;
78 		if(b > m) m = b;
79 		if(c > m) m = c;
80 		return m;
81 	}
82 	nothrow @safe @nogc pure
83 	bool startsWithInternal(string a, string b) {
84 		return (a.length >= b.length && a[0 .. b.length] == b);
85 	}
86 	string[] splitInternal(string a, char c) {
87 		string[] ret;
88 		size_t previous = 0;
89 		foreach(i, char ch; a) {
90 			if(ch == c) {
91 				ret ~= a[previous .. i];
92 				previous = i + 1;
93 			}
94 		}
95 		if(previous != a.length)
96 			ret ~= a[previous .. $];
97 		return ret;
98 	}
99 	nothrow @safe @nogc pure
100 	string stripInternal(string s) {
101 		foreach(i, char c; s)
102 		if(c != ' ' && c != '\t' && c != '\n') {
103 			s = s[i .. $];
104 			break;
105 		}
106 		for(int a = cast(int)(s.length - 1); a > 0; a--) {
107 			char c = s[a];
108 			if(c != ' ' && c != '\t' && c != '\n') {
109 				s = s[0 .. a + 1];
110 				break;
111 			}
112 		}
113 		
114 		return s;
115 	}
116 }
117 
118 // done with mini-phobos
119 
120 /// Represents an RGBA color
121 struct Color {
122 @safe:
123 	/++
124 		The color components are available as a static array, individual bytes, and a uint inside this union.
125 
126 		Since it is anonymous, you can use the inner members' names directly.
127 	+/
128 	union {
129 		ubyte[4] components; /// [r, g, b, a]
130 		
131 		/// Holder for rgba individual components.
132 		struct {
133 			ubyte r; /// red
134 			ubyte g; /// green
135 			ubyte b; /// blue
136 			ubyte a; /// alpha. 255 == opaque
137 		}
138 		
139 		uint asUint; /// The components as a single 32 bit value (beware of endian issues!)
140 	}
141 	
142 	/++
143 		Like the constructor, but this makes sure they are in range before casting. If they are out of range, it saturates: anything less than zero becomes zero and anything greater than 255 becomes 255.
144 	+/
145 	nothrow pure
146 	static Color fromIntegers(int red, int green, int blue, int alpha = 255) {
147 		return Color(clampToByte(red), clampToByte(green), clampToByte(blue), clampToByte(alpha));
148 	}
149 	
150 	/// Construct a color with the given values. They should be in range 0 <= x <= 255, where 255 is maximum intensity and 0 is minimum intensity.
151 	nothrow pure @nogc
152 	this(int red, int green, int blue, int alpha = 255) {
153 		this.r = cast(ubyte) red;
154 		this.g = cast(ubyte) green;
155 		this.b = cast(ubyte) blue;
156 		this.a = cast(ubyte) alpha;
157 	}
158 	
159 	/// Static convenience functions for common color names
160 	nothrow pure @nogc
161 	static Color transparent() { return Color(0, 0, 0, 0); }
162 	/// Ditto
163 	nothrow pure @nogc
164 	static Color white() { return Color(255, 255, 255); }
165 	/// Ditto
166 	nothrow pure @nogc
167 	static Color gray() { return Color(128, 128, 128); }
168 	/// Ditto
169 	nothrow pure @nogc
170 	static Color black() { return Color(0, 0, 0); }
171 	/// Ditto
172 	nothrow pure @nogc
173 	static Color red() { return Color(255, 0, 0); }
174 	/// Ditto
175 	nothrow pure @nogc
176 	static Color green() { return Color(0, 255, 0); }
177 	/// Ditto
178 	nothrow pure @nogc
179 	static Color blue() { return Color(0, 0, 255); }
180 	/// Ditto
181 	nothrow pure @nogc
182 	static Color yellow() { return Color(255, 255, 0); }
183 	/// Ditto
184 	nothrow pure @nogc
185 	static Color teal() { return Color(0, 255, 255); }
186 	/// Ditto
187 	nothrow pure @nogc
188 	static Color purple() { return Color(255, 0, 255); }
189 	/// Ditto
190 	nothrow pure @nogc
191 	static Color brown() { return Color(128, 64, 0); }
192 	
193 	/*
194 	ubyte[4] toRgbaArray() {
195 		return [r,g,b,a];
196 	}
197 	*/
198 	
199 	/// Return black-and-white color
200 	Color toBW() () {
201 		int intens = clampToByte(cast(int)(0.2126*r+0.7152*g+0.0722*b));
202 		return Color(intens, intens, intens, a);
203 	}
204 	
205 	/// Makes a string that matches CSS syntax for websites
206 	string toCssString() const {
207 		if(a == 255)
208 			return "#" ~ toHexInternal(r) ~ toHexInternal(g) ~ toHexInternal(b);
209 		else {
210 			return "rgba("~toInternal!string(r)~", "~toInternal!string(g)~", "~toInternal!string(b)~", "~toInternal!string(cast(real)a / 255.0)~")";
211 		}
212 	}
213 	
214 	/// Makes a hex string RRGGBBAA (aa only present if it is not 255)
215 	string toString() const {
216 		if(a == 255)
217 			return toCssString()[1 .. $];
218 		else
219 			return toRgbaHexString();
220 	}
221 	
222 	/// returns RRGGBBAA, even if a== 255
223 	string toRgbaHexString() const {
224 		return toHexInternal(r) ~ toHexInternal(g) ~ toHexInternal(b) ~ toHexInternal(a);
225 	}
226 	
227 	/// Gets a color by name, iff the name is one of the static members listed above
228 	static Color fromNameString(string s) {
229 		Color c;
230 		foreach(member; __traits(allMembers, Color)) {
231 			static if(__traits(compiles, c = __traits(getMember, Color, member))) {
232 				if(s == member)
233 					return __traits(getMember, Color, member);
234 			}
235 		}
236 		throw new Exception("Unknown color " ~ s);
237 	}
238 	
239 	/// Reads a CSS style string to get the color. Understands #rrggbb, rgba(), hsl(), and rrggbbaa
240 	static Color fromString(string s) {
241 		s = s.stripInternal();
242 		
243 		Color c;
244 		c.a = 255;
245 		
246 		// trying named colors via the static no-arg methods here
247 		foreach(member; __traits(allMembers, Color)) {
248 			static if(__traits(compiles, c = __traits(getMember, Color, member))) {
249 				if(s == member)
250 					return __traits(getMember, Color, member);
251 			}
252 		}
253 		
254 		// try various notations borrowed from CSS (though a little extended)
255 		
256 		// hsl(h,s,l,a) where h is degrees and s,l,a are 0 >= x <= 1.0
257 		if(s.startsWithInternal("hsl(") || s.startsWithInternal("hsla(")) {
258 			assert(s[$-1] == ')');
259 			s = s[s.startsWithInternal("hsl(") ? 4 : 5  .. $ - 1]; // the closing paren
260 			
261 			real[3] hsl;
262 			ubyte a = 255;
263 			
264 			auto parts = s.splitInternal(',');
265 			foreach(i, part; parts) {
266 				if(i < 3)
267 					hsl[i] = toInternal!real(part.stripInternal);
268 				else
269 					a = clampToByte(cast(int) (toInternal!real(part.stripInternal) * 255));
270 			}
271 			
272 			c = .fromHsl(hsl);
273 			c.a = a;
274 			
275 			return c;
276 		}
277 		
278 		// rgb(r,g,b,a) where r,g,b are 0-255 and a is 0-1.0
279 		if(s.startsWithInternal("rgb(") || s.startsWithInternal("rgba(")) {
280 			assert(s[$-1] == ')');
281 			s = s[s.startsWithInternal("rgb(") ? 4 : 5  .. $ - 1]; // the closing paren
282 			
283 			auto parts = s.splitInternal(',');
284 			foreach(i, part; parts) {
285 				// lol the loop-switch pattern
286 				auto v = toInternal!real(part.stripInternal);
287 				switch(i) {
288 					case 0: // red
289 						c.r = clampToByte(cast(int) v);
290 						break;
291 					case 1:
292 						c.g = clampToByte(cast(int) v);
293 						break;
294 					case 2:
295 						c.b = clampToByte(cast(int) v);
296 						break;
297 					case 3:
298 						c.a = clampToByte(cast(int) (v * 255));
299 						break;
300 					default: // ignore
301 				}
302 			}
303 			
304 			return c;
305 		}
306 		
307 		
308 		
309 		
310 		// otherwise let's try it as a hex string, really loosely
311 		
312 		if(s.length && s[0] == '#')
313 			s = s[1 .. $];
314 		
315 		// not a built in... do it as a hex string
316 		if(s.length >= 2) {
317 			c.r = fromHexInternal(s[0 .. 2]);
318 			s = s[2 .. $];
319 		}
320 		if(s.length >= 2) {
321 			c.g = fromHexInternal(s[0 .. 2]);
322 			s = s[2 .. $];
323 		}
324 		if(s.length >= 2) {
325 			c.b = fromHexInternal(s[0 .. 2]);
326 			s = s[2 .. $];
327 		}
328 		if(s.length >= 2) {
329 			c.a = fromHexInternal(s[0 .. 2]);
330 			s = s[2 .. $];
331 		}
332 		
333 		return c;
334 	}
335 	
336 	/// from hsl
337 	static Color fromHsl(real h, real s, real l) {
338 		return .fromHsl(h, s, l);
339 	}
340 	
341 	// this is actually branch-less for ints on x86, and even for longs on x86_64
342 	static ubyte clampToByte(T) (T n) pure nothrow @safe @nogc if (__traits(isIntegral, T)) {
343 		static if (__VERSION__ > 2067) pragma(inline, true);
344 		static if (T.sizeof == 2 || T.sizeof == 4) {
345 			static if (__traits(isUnsigned, T)) {
346 				return cast(ubyte)(n&0xff|(255-((-cast(int)(n < 256))>>24)));
347 			} else {
348 				n &= -cast(int)(n >= 0);
349 				return cast(ubyte)(n|((255-cast(int)n)>>31));
350 			}
351 		} else static if (T.sizeof == 1) {
352 			static assert(__traits(isUnsigned, T), "clampToByte: signed byte? no, really?");
353 			return cast(ubyte)n;
354 		} else static if (T.sizeof == 8) {
355 			static if (__traits(isUnsigned, T)) {
356 				return cast(ubyte)(n&0xff|(255-((-cast(long)(n < 256))>>56)));
357 			} else {
358 				n &= -cast(long)(n >= 0);
359 				return cast(ubyte)(n|((255-cast(long)n)>>63));
360 			}
361 		} else {
362 			static assert(false, "clampToByte: integer too big");
363 		}
364 	}
365 	
366 	/** this mixin can be used to alphablend two `uint` colors;
367 	 * `colu32name` is variable that holds color to blend,
368 	 * `destu32name` is variable that holds "current" color (from surface, for example).
369 	 * alpha value of `destu32name` doesn't matter.
370 	 * alpha value of `colu32name` means: 255 for replace color, 0 for keep `destu32name`.
371 	 *
372 	 * WARNING! This function does blending in RGB space, and RGB space is not linear!
373 	 */
374 	public enum ColorBlendMixinStr(string colu32name, string destu32name) = "{
375 		immutable uint a_tmp_ = (256-(255-(("~colu32name~")>>24)))&(-(1-(((255-(("~colu32name~")>>24))+1)>>8))); // to not loose bits, but 255 should become 0
376 		immutable uint dc_tmp_ = ("~destu32name~")&0xffffff;
377 		immutable uint srb_tmp_ = (("~colu32name~")&0xff00ff);
378 		immutable uint sg_tmp_ = (("~colu32name~")&0x00ff00);
379 		immutable uint drb_tmp_ = (dc_tmp_&0xff00ff);
380 		immutable uint dg_tmp_ = (dc_tmp_&0x00ff00);
381 		immutable uint orb_tmp_ = (drb_tmp_+(((srb_tmp_-drb_tmp_)*a_tmp_+0x800080)>>8))&0xff00ff;
382 		immutable uint og_tmp_ = (dg_tmp_+(((sg_tmp_-dg_tmp_)*a_tmp_+0x008000)>>8))&0x00ff00;
383 		("~destu32name~") = (orb_tmp_|og_tmp_)|0xff000000; /*&0xffffff;*/
384 	}";
385 	
386 	
387 	/// Perform alpha-blending of `fore` to this color, return new color.
388 	/// WARNING! This function does blending in RGB space, and RGB space is not linear!
389 	Color alphaBlend (Color fore) const pure nothrow @trusted @nogc {
390 		static if (__VERSION__ > 2067) pragma(inline, true);
391 		Color res;
392 		res.asUint = asUint;
393 		mixin(ColorBlendMixinStr!("fore.asUint", "res.asUint"));
394 		return res;
395 	}
396 }
397 
398 nothrow @safe
399 private string toHexInternal(ubyte b) {
400 	string s;
401 	if(b < 16)
402 		s ~= '0';
403 	else {
404 		ubyte t = (b & 0xf0) >> 4;
405 		if(t >= 10)
406 			s ~= 'A' + t - 10;
407 		else
408 			s ~= '0' + t;
409 		b &= 0x0f;
410 	}
411 	if(b >= 10)
412 		s ~= 'A' + b - 10;
413 	else
414 		s ~= '0' + b;
415 	
416 	return s;
417 }
418 
419 nothrow @safe @nogc pure
420 private ubyte fromHexInternal(string s) {
421 	int result = 0;
422 	
423 	int exp = 1;
424 	//foreach(c; retro(s)) { // FIXME: retro doesn't work right in dtojs
425 	foreach_reverse(c; s) {
426 		if(c >= 'A' && c <= 'F')
427 			result += exp * (c - 'A' + 10);
428 		else if(c >= 'a' && c <= 'f')
429 			result += exp * (c - 'a' + 10);
430 		else if(c >= '0' && c <= '9')
431 			result += exp * (c - '0');
432 		else
433 			// throw new Exception("invalid hex character: " ~ cast(char) c);
434 			return 0;
435 		
436 		exp *= 16;
437 	}
438 	
439 	return cast(ubyte) result;
440 }
441 
442 /// Converts hsl to rgb
443 Color fromHsl(real[3] hsl) {
444 	return fromHsl(hsl[0], hsl[1], hsl[2]);
445 }
446 
447 /// Converts hsl to rgb
448 Color fromHsl(real h, real s, real l, real a = 255) {
449 	h = h % 360;
450 	
451 	real C = (1 - absInternal(2 * l - 1)) * s;
452 	
453 	real hPrime = h / 60;
454 	
455 	real X = C * (1 - absInternal(hPrime % 2 - 1));
456 	
457 	real r, g, b;
458 	
459 	if(h is real.nan)
460 		r = g = b = 0;
461 	else if (hPrime >= 0 && hPrime < 1) {
462 		r = C;
463 		g = X;
464 		b = 0;
465 	} else if (hPrime >= 1 && hPrime < 2) {
466 		r = X;
467 		g = C;
468 		b = 0;
469 	} else if (hPrime >= 2 && hPrime < 3) {
470 		r = 0;
471 		g = C;
472 		b = X;
473 	} else if (hPrime >= 3 && hPrime < 4) {
474 		r = 0;
475 		g = X;
476 		b = C;
477 	} else if (hPrime >= 4 && hPrime < 5) {
478 		r = X;
479 		g = 0;
480 		b = C;
481 	} else if (hPrime >= 5 && hPrime < 6) {
482 		r = C;
483 		g = 0;
484 		b = X;
485 	}
486 	
487 	real m = l - C / 2;
488 	
489 	r += m;
490 	g += m;
491 	b += m;
492 	
493 	return Color(
494 		cast(int)(r * 255),
495 		cast(int)(g * 255),
496 		cast(int)(b * 255),
497 		cast(int)(a));
498 }
499 
500 /// Converts an RGB color into an HSL triplet. useWeightedLightness will try to get a better value for luminosity for the human eye, which is more sensitive to green than red and more to red than blue. If it is false, it just does average of the rgb.
501 real[3] toHsl(Color c, bool useWeightedLightness = false) {
502 	real r1 = cast(real) c.r / 255;
503 	real g1 = cast(real) c.g / 255;
504 	real b1 = cast(real) c.b / 255;
505 	
506 	real maxColor = maxInternal(r1, g1, b1);
507 	real minColor = minInternal(r1, g1, b1);
508 	
509 	real L = (maxColor + minColor) / 2 ;
510 	if(useWeightedLightness) {
511 		// the colors don't affect the eye equally
512 		// this is a little more accurate than plain HSL numbers
513 		L = 0.2126*r1 + 0.7152*g1 + 0.0722*b1;
514 	}
515 	real S = 0;
516 	real H = 0;
517 	if(maxColor != minColor) {
518 		if(L < 0.5) {
519 			S = (maxColor - minColor) / (maxColor + minColor);
520 		} else {
521 			S = (maxColor - minColor) / (2.0 - maxColor - minColor);
522 		}
523 		if(r1 == maxColor) {
524 			H = (g1-b1) / (maxColor - minColor);
525 		} else if(g1 == maxColor) {
526 			H = 2.0 + (b1 - r1) / (maxColor - minColor);
527 		} else {
528 			H = 4.0 + (r1 - g1) / (maxColor - minColor);
529 		}
530 	}
531 	
532 	H = H * 60;
533 	if(H < 0){
534 		H += 360;
535 	}
536 	
537 	return [H, S, L]; 
538 }
539 
540 /// .
541 Color lighten(Color c, real percentage) {
542 	auto hsl = toHsl(c);
543 	hsl[2] *= (1 + percentage);
544 	if(hsl[2] > 1)
545 		hsl[2] = 1;
546 	return fromHsl(hsl);
547 }
548 
549 /// .
550 Color darken(Color c, real percentage) {
551 	auto hsl = toHsl(c);
552 	hsl[2] *= (1 - percentage);
553 	return fromHsl(hsl);
554 }
555 
556 /// for light colors, call darken. for dark colors, call lighten.
557 /// The goal: get toward center grey.
558 Color moderate(Color c, real percentage) {
559 	auto hsl = toHsl(c);
560 	if(hsl[2] > 0.5)
561 		hsl[2] *= (1 - percentage);
562 	else {
563 		if(hsl[2] <= 0.01) // if we are given black, moderating it means getting *something* out
564 			hsl[2] = percentage;
565 		else
566 			hsl[2] *= (1 + percentage);
567 	}
568 	if(hsl[2] > 1)
569 		hsl[2] = 1;
570 	return fromHsl(hsl);
571 }
572 
573 /// the opposite of moderate. Make darks darker and lights lighter
574 Color extremify(Color c, real percentage) {
575 	auto hsl = toHsl(c, true);
576 	if(hsl[2] < 0.5)
577 		hsl[2] *= (1 - percentage);
578 	else
579 		hsl[2] *= (1 + percentage);
580 	if(hsl[2] > 1)
581 		hsl[2] = 1;
582 	return fromHsl(hsl);
583 }
584 
585 /// Move around the lightness wheel, trying not to break on moderate things
586 Color oppositeLightness(Color c) {
587 	auto hsl = toHsl(c);
588 	
589 	auto original = hsl[2];
590 	
591 	if(original > 0.4 && original < 0.6)
592 		hsl[2] = 0.8 - original; // so it isn't quite the same
593 	else
594 		hsl[2] = 1 - original;
595 	
596 	return fromHsl(hsl);
597 }
598 
599 /// Try to determine a text color - either white or black - based on the input
600 Color makeTextColor(Color c) {
601 	auto hsl = toHsl(c, true); // give green a bonus for contrast
602 	if(hsl[2] > 0.71)
603 		return Color(0, 0, 0);
604 	else
605 		return Color(255, 255, 255);
606 }
607 
608 // These provide functional access to hsl manipulation; useful if you need a delegate
609 
610 Color setLightness(Color c, real lightness) {
611 	auto hsl = toHsl(c);
612 	hsl[2] = lightness;
613 	return fromHsl(hsl);
614 }
615 
616 
617 ///
618 Color rotateHue(Color c, real degrees) {
619 	auto hsl = toHsl(c);
620 	hsl[0] += degrees;
621 	return fromHsl(hsl);
622 }
623 
624 ///
625 Color setHue(Color c, real hue) {
626 	auto hsl = toHsl(c);
627 	hsl[0] = hue;
628 	return fromHsl(hsl);
629 }
630 
631 ///
632 Color desaturate(Color c, real percentage) {
633 	auto hsl = toHsl(c);
634 	hsl[1] *= (1 - percentage);
635 	return fromHsl(hsl);
636 }
637 
638 ///
639 Color saturate(Color c, real percentage) {
640 	auto hsl = toHsl(c);
641 	hsl[1] *= (1 + percentage);
642 	if(hsl[1] > 1)
643 		hsl[1] = 1;
644 	return fromHsl(hsl);
645 }
646 
647 ///
648 Color setSaturation(Color c, real saturation) {
649 	auto hsl = toHsl(c);
650 	hsl[1] = saturation;
651 	return fromHsl(hsl);
652 }
653 
654 
655 /*
656 void main(string[] args) {
657 	auto color1 = toHsl(Color(255, 0, 0));
658 	auto color = fromHsl(color1[0] + 60, color1[1], color1[2]);
659 
660 	writefln("#%02x%02x%02x", color.r, color.g, color.b);
661 }
662 */
663 
664 /* Color algebra functions */
665 
666 /* Alpha putpixel looks like this:
667 
668 void putPixel(Image i, Color c) {
669 	Color b;
670 	b.r = i.data[(y * i.width + x) * bpp + 0];
671 	b.g = i.data[(y * i.width + x) * bpp + 1];
672 	b.b = i.data[(y * i.width + x) * bpp + 2];
673 	b.a = i.data[(y * i.width + x) * bpp + 3];
674 
675 	float ca = cast(float) c.a / 255;
676 
677 	i.data[(y * i.width + x) * bpp + 0] = alpha(c.r, ca, b.r);
678 	i.data[(y * i.width + x) * bpp + 1] = alpha(c.g, ca, b.g);
679 	i.data[(y * i.width + x) * bpp + 2] = alpha(c.b, ca, b.b);
680 	i.data[(y * i.width + x) * bpp + 3] = alpha(c.a, ca, b.a);
681 }
682 
683 ubyte alpha(ubyte c1, float alpha, ubyte onto) {
684 	auto got = (1 - alpha) * onto + alpha * c1;
685 
686 	if(got > 255)
687 		return 255;
688 	return cast(ubyte) got;
689 }
690 
691 So, given the background color and the resultant color, what was
692 composited on to it?
693 */
694 
695 ///
696 ubyte unalpha(ubyte colorYouHave, float alpha, ubyte backgroundColor) {
697 	// resultingColor = (1-alpha) * backgroundColor + alpha * answer
698 	auto resultingColorf = cast(float) colorYouHave;
699 	auto backgroundColorf = cast(float) backgroundColor;
700 	
701 	auto answer = (resultingColorf - backgroundColorf + alpha * backgroundColorf) / alpha;
702 	return Color.clampToByte(cast(int) answer);
703 }
704 
705 ///
706 ubyte makeAlpha(ubyte colorYouHave, ubyte backgroundColor/*, ubyte foreground = 0x00*/) {
707 	//auto foregroundf = cast(float) foreground;
708 	auto foregroundf = 0.00f;
709 	auto colorYouHavef = cast(float) colorYouHave;
710 	auto backgroundColorf = cast(float) backgroundColor;
711 	
712 	// colorYouHave = backgroundColorf - alpha * backgroundColorf + alpha * foregroundf
713 	auto alphaf = 1 - colorYouHave / backgroundColorf;
714 	alphaf *= 255;
715 	
716 	return Color.clampToByte(cast(int) alphaf);
717 }
718 
719 
720 int fromHex(string s) {
721 	int result = 0;
722 	
723 	int exp = 1;
724 	// foreach(c; retro(s)) {
725 	foreach_reverse(c; s) {
726 		if(c >= 'A' && c <= 'F')
727 			result += exp * (c - 'A' + 10);
728 		else if(c >= 'a' && c <= 'f')
729 			result += exp * (c - 'a' + 10);
730 		else if(c >= '0' && c <= '9')
731 			result += exp * (c - '0');
732 		else
733 			throw new Exception("invalid hex character: " ~ cast(char) c);
734 		
735 		exp *= 16;
736 	}
737 	
738 	return result;
739 }
740 
741 ///
742 Color colorFromString(string s) {
743 	if(s.length == 0)
744 		return Color(0,0,0,255);
745 	if(s[0] == '#')
746 		s = s[1..$];
747 	assert(s.length == 6 || s.length == 8);
748 	
749 	Color c;
750 	
751 	c.r = cast(ubyte) fromHex(s[0..2]);
752 	c.g = cast(ubyte) fromHex(s[2..4]);
753 	c.b = cast(ubyte) fromHex(s[4..6]);
754 	if(s.length == 8)
755 		c.a = cast(ubyte) fromHex(s[6..8]);
756 	else
757 		c.a = 255;
758 	
759 	return c;
760 }
761 
762 /*
763 import browser.window;
764 import std.conv;
765 void main() {
766 	import browser.document;
767 	foreach(ele; document.querySelectorAll("input")) {
768 		ele.addEventListener("change", {
769 			auto h = toInternal!real(document.querySelector("input[name=h]").value);
770 			auto s = toInternal!real(document.querySelector("input[name=s]").value);
771 			auto l = toInternal!real(document.querySelector("input[name=l]").value);
772 
773 			Color c = Color.fromHsl(h, s, l);
774 
775 			auto e = document.getElementById("example");
776 			e.style.backgroundColor = c.toCssString();
777 
778 			// JSElement __js_this;
779 			// __js_this.style.backgroundColor = c.toCssString();
780 		}, false);
781 	}
782 }
783 */
784 
785 
786 
787 /**
788 	This provides two image classes and a bunch of functions that work on them.
789 
790 	Why are they separate classes? I think the operations on the two of them
791 	are necessarily different. There's a whole bunch of operations that only
792 	really work on truecolor (blurs, gradients), and a few that only work
793 	on indexed images (palette swaps).
794 
795 	Even putpixel is pretty different. On indexed, it is a palette entry's
796 	index number. On truecolor, it is the actual color.
797 
798 	A greyscale image is the weird thing in the middle. It is truecolor, but
799 	fits in the same size as indexed. Still, I'd say it is a specialization
800 	of truecolor.
801 
802 	There is a subset that works on both
803 
804 */
805 
806 /// An image in memory
807 interface MemoryImage {
808 	//IndexedImage convertToIndexedImage() const;
809 	//TrueColorImage convertToTrueColor() const;
810 	
811 	/// gets it as a TrueColorImage. May return this or may do a conversion and return a new image
812 	TrueColorImage getAsTrueColorImage();
813 	
814 	/// Image width, in pixels
815 	int width() const;
816 	
817 	/// Image height, in pixels
818 	int height() const;
819 	
820 	/// Get image pixel. Slow, but returns valid RGBA color (completely transparent for off-image pixels).
821 	Color getPixel(int x, int y) const;
822 	
823 	/// Set image pixel.
824 	void setPixel(int x, int y, in Color clr);
825 	
826 	/// Load image from file. This will import arsd.image to do the actual work, and cost nothing if you don't use it.
827 	static MemoryImage fromImage(T : const(char)[]) (T filename) @trusted {
828 		static if (__traits(compiles, (){import arsd.image;})) {
829 			// yay, we have image loader here, try it!
830 			import arsd.image;
831 			return loadImageFromFile(filename);
832 		} else {
833 			static assert(0, "please provide 'arsd.image' to load images!");
834 		}
835 	}
836 	
837 	/// Convenient alias for `fromImage`
838 	alias fromImageFile = fromImage;
839 }
840 
841 /// An image that consists of indexes into a color palette. Use [getAsTrueColorImage]() if you don't care about palettes
842 class IndexedImage : MemoryImage {
843 	bool hasAlpha;
844 	
845 	/// .
846 	Color[] palette;
847 	/// the data as indexes into the palette. Stored left to right, top to bottom, no padding.
848 	ubyte[] data;
849 	
850 	/// .
851 	override int width() const {
852 		return _width;
853 	}
854 	
855 	/// .
856 	override int height() const {
857 		return _height;
858 	}
859 	
860 	override Color getPixel(int x, int y) const @trusted {
861 		if (x >= 0 && y >= 0 && x < _width && y < _height) {
862 			uint pos = y*_width+x;
863 			if (pos >= data.length) return Color(0, 0, 0, 0);
864 			ubyte b = data.ptr[pos];
865 			if (b >= palette.length) return Color(0, 0, 0, 0);
866 			return palette.ptr[b];
867 		} else {
868 			return Color(0, 0, 0, 0);
869 		}
870 	}
871 	
872 	override void setPixel(int x, int y, in Color clr) @trusted {
873 		if (x >= 0 && y >= 0 && x < _width && y < _height) {
874 			uint pos = y*_width+x;
875 			if (pos >= data.length) return;
876 			ubyte pidx = findNearestColor(palette, clr);
877 			if (palette.length < 255 &&
878 				(palette.ptr[pidx].r != clr.r || palette.ptr[pidx].g != clr.g || palette.ptr[pidx].b != clr.b || palette.ptr[pidx].a != clr.a)) {
879 				// add new color
880 				pidx = addColor(clr);
881 			}
882 			data.ptr[pos] = pidx;
883 		}
884 	}
885 	
886 	private int _width;
887 	private int _height;
888 	
889 	/// .
890 	this(int w, int h) {
891 		_width = w;
892 		_height = h;
893 		data = new ubyte[w*h];
894 	}
895 	
896 	/*
897 	void resize(int w, int h, bool scale) {
898 
899 	}
900 	*/
901 	
902 	/// returns a new image
903 	override TrueColorImage getAsTrueColorImage() {
904 		return convertToTrueColor();
905 	}
906 	
907 	/// Creates a new TrueColorImage based on this data
908 	TrueColorImage convertToTrueColor() const {
909 		auto tci = new TrueColorImage(width, height);
910 		foreach(i, b; data) {
911 			/*
912 			if(b >= palette.length) {
913 				string fuckyou;
914 				fuckyou ~= b + '0';
915 				fuckyou ~= " ";
916 				fuckyou ~= palette.length + '0';
917 				assert(0, fuckyou);
918 			}
919 			*/
920 			tci.imageData.colors[i] = palette[b];
921 		}
922 		return tci;
923 	}
924 	
925 	/// Gets an exact match, if possible, adds if not. See also: the findNearestColor free function.
926 	ubyte getOrAddColor(Color c) {
927 		foreach(i, co; palette) {
928 			if(c == co)
929 				return cast(ubyte) i;
930 		}
931 		
932 		return addColor(c);
933 	}
934 	
935 	/// Number of colors currently in the palette (note: palette entries are not necessarily used in the image data)
936 	int numColors() const {
937 		return cast(int) palette.length;
938 	}
939 	
940 	/// Adds an entry to the palette, returning its inded
941 	ubyte addColor(Color c) {
942 		assert(palette.length < 256);
943 		if(c.a != 255)
944 			hasAlpha = true;
945 		palette ~= c;
946 		
947 		return cast(ubyte) (palette.length - 1);
948 	}
949 }
950 
951 /// An RGBA array of image data. Use the free function quantize() to convert to an IndexedImage
952 class TrueColorImage : MemoryImage {
953 	//	bool hasAlpha;
954 	//	bool isGreyscale;
955 	
956 	//ubyte[] data; // stored as rgba quads, upper left to right to bottom
957 	/// .
958 	struct Data {
959 		ubyte[] bytes; /// the data as rgba bytes. Stored left to right, top to bottom, no padding.
960 		// the union is no good because the length of the struct is wrong!
961 		
962 		/// the same data as Color structs
963 		@trusted // the cast here is typically unsafe, but it is ok
964 		// here because I guarantee the layout, note the static assert below
965 		@property inout(Color)[] colors() inout {
966 			return cast(inout(Color)[]) bytes;
967 		}
968 		
969 		static assert(Color.sizeof == 4);
970 	}
971 	
972 	/// .
973 	Data imageData;
974 	alias imageData.bytes data;
975 	
976 	int _width;
977 	int _height;
978 	
979 	/// .
980 	override int width() const { return _width; }
981 	///.
982 	override int height() const { return _height; }
983 	
984 	override Color getPixel(int x, int y) const @trusted {
985 		if (x >= 0 && y >= 0 && x < _width && y < _height) {
986 			uint pos = y*_width+x;
987 			return imageData.colors.ptr[pos];
988 		} else {
989 			return Color(0, 0, 0, 0);
990 		}
991 	}
992 	
993 	override void setPixel(int x, int y, in Color clr) @trusted {
994 		if (x >= 0 && y >= 0 && x < _width && y < _height) {
995 			uint pos = y*_width+x;
996 			if (pos < imageData.bytes.length/4) imageData.colors.ptr[pos] = clr;
997 		}
998 	}
999 	
1000 	/// .
1001 	this(int w, int h) {
1002 		_width = w;
1003 		_height = h;
1004 		imageData.bytes = new ubyte[w*h*4];
1005 	}
1006 	
1007 	/// Creates with existing data. The data pointer is stored here.
1008 	this(int w, int h, ubyte[] data) {
1009 		_width = w;
1010 		_height = h;
1011 		assert(data.length == w * h * 4);
1012 		imageData.bytes = data;
1013 	}
1014 	
1015 	/// Returns this
1016 	override TrueColorImage getAsTrueColorImage() {
1017 		return this;
1018 	}
1019 }
1020 
1021 /// Converts true color to an indexed image. It uses palette as the starting point, adding entries
1022 /// until maxColors as needed. If palette is null, it creates a whole new palette.
1023 ///
1024 /// After quantizing the image, it applies a dithering algorithm.
1025 ///
1026 /// This is not written for speed.
1027 IndexedImage quantize(in TrueColorImage img, Color[] palette = null, in int maxColors = 256)
1028 	// this is just because IndexedImage assumes ubyte palette values
1029 in { assert(maxColors <= 256); }
1030 body {
1031 	int[Color] uses;
1032 	foreach(pixel; img.imageData.colors) {
1033 		if(auto i = pixel in uses) {
1034 			(*i)++;
1035 		} else {
1036 			uses[pixel] = 1;
1037 		}
1038 	}
1039 	
1040 	struct ColorUse {
1041 		Color c;
1042 		int uses;
1043 		//string toString() { import std.conv; return c.toCssString() ~ " x " ~ to!string(uses); }
1044 		int opCmp(ref const ColorUse co) const {
1045 			return co.uses - uses;
1046 		}
1047 	}
1048 	
1049 	ColorUse[] sorted;
1050 	
1051 	foreach(color, count; uses)
1052 		sorted ~= ColorUse(color, count);
1053 	
1054 	uses = null;
1055 	version(no_phobos)
1056 		sorted = sorted.sort;
1057 	else {
1058 		import std.algorithm : sort;
1059 		sort(sorted);
1060 	}
1061 	
1062 	ubyte[Color] paletteAssignments;
1063 	foreach(idx, entry; palette)
1064 		paletteAssignments[entry] = cast(ubyte) idx;
1065 	
1066 	// For the color assignments from the image, I do multiple passes, decreasing the acceptable
1067 	// distance each time until we're full.
1068 	
1069 	// This is probably really slow.... but meh it gives pretty good results.
1070 	
1071 	auto ddiff = 32;
1072 outer: for(int d1 = 128; d1 >= 0; d1 -= ddiff) {
1073 		auto minDist = d1*d1;
1074 		if(d1 <= 64)
1075 			ddiff = 16;
1076 		if(d1 <= 32)
1077 			ddiff = 8;
1078 		foreach(possibility; sorted) {
1079 			if(palette.length == maxColors)
1080 				break;
1081 			if(palette.length) {
1082 				auto co = palette[findNearestColor(palette, possibility.c)];
1083 				auto pixel = possibility.c;
1084 				
1085 				auto dr = cast(int) co.r - pixel.r;
1086 				auto dg = cast(int) co.g - pixel.g;
1087 				auto db = cast(int) co.b - pixel.b;
1088 				
1089 				auto dist = dr*dr + dg*dg + db*db;
1090 				// not good enough variety to justify an allocation yet
1091 				if(dist < minDist)
1092 					continue;
1093 			}
1094 			paletteAssignments[possibility.c] = cast(ubyte) palette.length;
1095 			palette ~= possibility.c;
1096 		}
1097 	}
1098 	
1099 	// Final pass: just fill in any remaining space with the leftover common colors
1100 	while(palette.length < maxColors && sorted.length) {
1101 		if(sorted[0].c !in paletteAssignments) {
1102 			paletteAssignments[sorted[0].c] = cast(ubyte) palette.length;
1103 			palette ~= sorted[0].c;
1104 		}
1105 		sorted = sorted[1 .. $];
1106 	}
1107 	
1108 	
1109 	bool wasPerfect = true;
1110 	auto newImage = new IndexedImage(img.width, img.height);
1111 	newImage.palette = palette;
1112 	foreach(idx, pixel; img.imageData.colors) {
1113 		if(auto p = pixel in paletteAssignments)
1114 			newImage.data[idx] = *p;
1115 		else {
1116 			// gotta find the closest one...
1117 			newImage.data[idx] = findNearestColor(palette, pixel);
1118 			wasPerfect = false;
1119 		}
1120 	}
1121 	
1122 	if(!wasPerfect)
1123 		floydSteinbergDither(newImage, img);
1124 	
1125 	return newImage;
1126 }
1127 
1128 /// Finds the best match for pixel in palette (currently by checking for minimum euclidean distance in rgb colorspace)
1129 ubyte findNearestColor(in Color[] palette, in Color pixel) {
1130 	int best = 0;
1131 	int bestDistance = int.max;
1132 	foreach(pe, co; palette) {
1133 		auto dr = cast(int) co.r - pixel.r;
1134 		auto dg = cast(int) co.g - pixel.g;
1135 		auto db = cast(int) co.b - pixel.b;
1136 		int dist = dr*dr + dg*dg + db*db;
1137 		
1138 		if(dist < bestDistance) {
1139 			best = cast(int) pe;
1140 			bestDistance = dist;
1141 		}
1142 	}
1143 	
1144 	return cast(ubyte) best;
1145 }
1146 
1147 /+
1148 
1149 // Quantizing and dithering test program
1150 
1151 void main( ){
1152 /*
1153 	auto img = new TrueColorImage(256, 32);
1154 	foreach(y; 0 .. img.height) {
1155 		foreach(x; 0 .. img.width) {
1156 			img.imageData.colors[x + y * img.width] = Color(x, y * (255 / img.height), 0);
1157 		}
1158 	}
1159 */
1160 
1161 TrueColorImage img;
1162 
1163 {
1164 
1165 import arsd.png;
1166 
1167 struct P {
1168 	ubyte[] range;
1169 	void put(ubyte[] a) { range ~= a; }
1170 }
1171 
1172 P range;
1173 import std.algorithm;
1174 
1175 import std.stdio;
1176 writePngLazy(range, pngFromBytes(File("/home/me/nyesha.png").byChunk(4096)).byRgbaScanline.map!((line) {
1177 	foreach(ref pixel; line.pixels) {
1178 	continue;
1179 		auto sum = cast(int) pixel.r + pixel.g + pixel.b;
1180 		ubyte a = cast(ubyte)(sum / 3);
1181 		pixel.r = a;
1182 		pixel.g = a;
1183 		pixel.b = a;
1184 	}
1185 	return line;
1186 }));
1187 
1188 img = imageFromPng(readPng(range.range)).getAsTrueColorImage;
1189 
1190 
1191 }
1192 
1193 
1194 
1195 	auto qimg = quantize(img, null, 2);
1196 
1197 	import arsd.simpledisplay;
1198 	auto win = new SimpleWindow(img.width, img.height * 3);
1199 	auto painter = win.draw();
1200 	painter.drawImage(Point(0, 0), Image.fromMemoryImage(img));
1201 	painter.drawImage(Point(0, img.height), Image.fromMemoryImage(qimg));
1202 	floydSteinbergDither(qimg, img);
1203 	painter.drawImage(Point(0, img.height * 2), Image.fromMemoryImage(qimg));
1204 	win.eventLoop(0);
1205 }
1206 +/
1207 
1208 /+
1209 /// If the background is transparent, it simply erases the alpha channel.
1210 void removeTransparency(IndexedImage img, Color background)
1211 +/
1212 
1213 /// Perform alpha-blending of `fore` to this color, return new color.
1214 /// WARNING! This function does blending in RGB space, and RGB space is not linear!
1215 Color alphaBlend(Color foreground, Color background) pure nothrow @safe @nogc {
1216 	static if (__VERSION__ > 2067) pragma(inline, true);
1217 	return background.alphaBlend(foreground);
1218 }
1219 
1220 /*
1221 /// Reduces the number of colors in a palette.
1222 void reducePaletteSize(IndexedImage img, int maxColors = 16) {
1223 
1224 }
1225 */
1226 
1227 // I think I did this wrong... but the results aren't too bad so the bug can't be awful.
1228 /// Dithers img in place to look more like original.
1229 void floydSteinbergDither(IndexedImage img, in TrueColorImage original) {
1230 	assert(img.width == original.width);
1231 	assert(img.height == original.height);
1232 	
1233 	auto buffer = new Color[](original.imageData.colors.length);
1234 	
1235 	int x, y;
1236 	
1237 	foreach(idx, c; original.imageData.colors) {
1238 		auto n = img.palette[img.data[idx]];
1239 		int errorR = cast(int) c.r - n.r;
1240 		int errorG = cast(int) c.g - n.g;
1241 		int errorB = cast(int) c.b - n.b;
1242 		
1243 		void doit(int idxOffset, int multiplier) {
1244 			//	if(idx + idxOffset < buffer.length)
1245 			buffer[idx + idxOffset] = Color.fromIntegers(
1246 				c.r + multiplier * errorR / 16,
1247 				c.g + multiplier * errorG / 16,
1248 				c.b + multiplier * errorB / 16,
1249 				c.a
1250 				);
1251 		}
1252 		
1253 		if((x+1) != original.width)
1254 			doit(1, 7);
1255 		if((y+1) != original.height) {
1256 			if(x != 0)
1257 				doit(-1 + img.width, 3);
1258 			doit(img.width, 5);
1259 			if(x+1 != original.width)
1260 				doit(1 + img.width, 1);
1261 		}
1262 		
1263 		img.data[idx] = findNearestColor(img.palette, buffer[idx]);
1264 		
1265 		x++;
1266 		if(x == original.width) {
1267 			x = 0;
1268 			y++;
1269 		}
1270 	}
1271 }
1272 
1273 // these are just really useful in a lot of places where the color/image functions are used,
1274 // so I want them available with Color
1275 ///
1276 struct Point {
1277 	int x; ///
1278 	int y; ///
1279 	
1280 	pure const nothrow @safe:
1281 	
1282 	Point opBinary(string op)(in Point rhs) {
1283 		return Point(mixin("x" ~ op ~ "rhs.x"), mixin("y" ~ op ~ "rhs.y"));
1284 	}
1285 	
1286 	Point opBinary(string op)(int rhs) {
1287 		return Point(mixin("x" ~ op ~ "rhs"), mixin("y" ~ op ~ "rhs"));
1288 	}
1289 }
1290 
1291 ///
1292 struct Size {
1293 	int width; ///
1294 	int height; ///
1295 }
1296 
1297 ///
1298 struct Rectangle {
1299 	int left; ///
1300 	int top; ///
1301 	int right; ///
1302 	int bottom; ///
1303 	
1304 	pure const nothrow @safe:
1305 	
1306 	///
1307 	this(int left, int top, int right, int bottom) {
1308 		this.left = left;
1309 		this.top = top;
1310 		this.right = right;
1311 		this.bottom = bottom;
1312 	}
1313 	
1314 	///
1315 	this(in Point upperLeft, in Point lowerRight) {
1316 		this(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y);
1317 	}
1318 	
1319 	///
1320 	this(in Point upperLeft, in Size size) {
1321 		this(upperLeft.x, upperLeft.y, upperLeft.x + size.width, upperLeft.y + size.height);
1322 	}
1323 	
1324 	///
1325 	@property Point upperLeft() {
1326 		return Point(left, top);
1327 	}
1328 	
1329 	///
1330 	@property Point lowerRight() {
1331 		return Point(right, bottom);
1332 	}
1333 	
1334 	///
1335 	@property Size size() {
1336 		return Size(width, height);
1337 	}
1338 	
1339 	///
1340 	@property int width() {
1341 		return right - left;
1342 	}
1343 	
1344 	///
1345 	@property int height() {
1346 		return bottom - top;
1347 	}
1348 	
1349 	/// Returns true if this rectangle entirely contains the other
1350 	bool contains(in Rectangle r) {
1351 		return contains(r.upperLeft) && contains(r.lowerRight);
1352 	}
1353 	
1354 	/// ditto
1355 	bool contains(in Point p) {
1356 		return (p.x >= left && p.y < right && p.y >= top && p.y < bottom);
1357 	}
1358 	
1359 	/// Returns true of the two rectangles at any point overlap
1360 	bool overlaps(in Rectangle r) {
1361 		// the -1 in here are because right and top are exclusive
1362 		return !((right-1) < r.left || (r.right-1) < left || (bottom-1) < r.top || (r.bottom-1) < top);
1363 	}
1364 }
1365 
1366 /++
1367 	Implements a flood fill algorithm, like the bucket tool in
1368 	MS Paint.
1369 
1370 	Params:
1371 		what = the canvas to work with, arranged as top to bottom, left to right elements
1372 		width = the width of the canvas
1373 		height = the height of the canvas
1374 		target = the type to replace. You may pass the existing value if you want to do what Paint does
1375 		replacement = the replacement value
1376 		x = the x-coordinate to start the fill (think of where the user clicked in Paint)
1377 		y = the y-coordinate to start the fill
1378 		additionalCheck = A custom additional check to perform on each square before continuing. Returning true means keep flooding, returning false means stop.
1379 +/
1380 void floodFill(T)(
1381 	T[] what, int width, int height, // the canvas to inspect
1382 	T target, T replacement, // fill params
1383 	int x, int y, bool delegate(int x, int y) @safe additionalCheck) // the node
1384 {
1385 	T node = what[y * width + x];
1386 	
1387 	if(target == replacement) return;
1388 	
1389 	if(node != target) return;
1390 	
1391 	if(additionalCheck is null)
1392 		additionalCheck = (int, int) => true;
1393 	
1394 	if(!additionalCheck(x, y))
1395 		return;
1396 	
1397 	what[y * width + x] = replacement;
1398 	
1399 	if(x)
1400 		floodFill(what, width, height, target, replacement,
1401 			x - 1, y, additionalCheck);
1402 	
1403 	if(x != width - 1)
1404 		floodFill(what, width, height, target, replacement,
1405 			x + 1, y, additionalCheck);
1406 	
1407 	if(y)
1408 		floodFill(what, width, height, target, replacement,
1409 			x, y - 1, additionalCheck);
1410 	
1411 	if(y != height - 1)
1412 		floodFill(what, width, height, target, replacement,
1413 			x, y + 1, additionalCheck);
1414 }
1415 
1416 // for scripting, so you can tag it without strictly needing to import arsd.jsvar
1417 enum arsd_jsvar_compatible = "arsd_jsvar_compatible";