1 #include "Core.h"
2 
3 namespace Upp {
4 
RGBtoHSV(double r,double g,double b,double & h,double & s,double & v)5 void RGBtoHSV(double r, double g, double b, double& h, double& s, double& v)
6 {
7 	double delta;
8 	if((v = max(r, max(g, b))) == 0 || (delta = v - min(r, min(g, b))) == 0)
9 	{
10 		h = s = 0;
11 		return;
12 	}
13 	s = delta / v;
14 	delta *= 6;
15 	if(g == v)
16 		h = 1 / 3.0 + (b - r) / delta;
17 	else if(b == v)
18 		h = 2 / 3.0 + (r - g) / delta;
19 	else
20 		if((h = (g - b) / delta) < 0)
21 			h += 1;
22 }
23 
HSVtoRGB(double h,double s,double v,double & r,double & g,double & b)24 void HSVtoRGB(double h, double s, double v, double& r, double& g, double& b)
25 {
26 	if(s == 0)
27 	{
28 		r = g = b = v;
29 		return;
30 	}
31 	double rem = fmod(h *= 6, 1);
32 	double p = v * (1 - s);
33 	double q = v * (1 - s * rem);
34 	double t = v * (1 - s * (1 - rem));
35 	switch(ffloor(h))
36 	{
37 	default: NEVER(); // invalid color!
38 	case 6:
39 	case 0: r = v; g = t; b = p; break;
40 	case 1: r = q; g = v; b = p; break;
41 	case 2: r = p; g = v; b = t; break;
42 	case 3: r = p; g = q; b = v; break;
43 	case 4: r = t; g = p; b = v; break;
44 	case -1:
45 	case 5: r = v; g = p; b = q; break;
46 	}
47 }
48 
HsvColorf(double h,double s,double v)49 Color HsvColorf(double h, double s, double v)
50 {
51 	double r, g, b;
52 	HSVtoRGB(h, s, v, r, g, b);
53 	return Color(min(int(r * 255), 255), min(int(g * 255), 255), min(int(b * 255), 255));
54 }
55 
CMYKtoRGB(double c,double m,double y,double k,double & r,double & g,double & b)56 void CMYKtoRGB(double c, double m, double y, double k, double& r, double& g, double& b)
57 {
58 	k = clamp(k, 0.0, 1.0);
59 	r = (1 - c) * (1 - k);
60 	g = (1 - m) * (1 - k);
61 	b = (1 - y) * (1 - k);
62 }
63 
RGBtoCMYK(double r,double g,double b,double & c,double & m,double & y,double & k)64 void RGBtoCMYK(double r, double g, double b, double& c, double& m, double& y, double& k)
65 {
66 	k = 1 - max(max(r, g), b);
67 	c = (1 - r - k) / (1 - k);
68 	m = (1 - g - k) / (1 - k);
69 	y = (1 - b - k) / (1 - k);
70 }
71 
CmykColorf(double c,double m,double y,double k)72 Color CmykColorf(double c, double m, double y, double k)
73 {
74 	double r, g, b;
75 	CMYKtoRGB(c, m, y, k, r, g, b);
76 	return Color(min(int(r * 255), 255), min(int(g * 255), 255), min(int(b * 255), 255));
77 }
78 
Get() const79 dword Color::Get() const
80 {
81 	if(IsNullInstance()) return 0;
82 	dword c = color;
83 	return c & 0xffffff;
84 }
85 
86 template <>
AsString(const RGBA & c)87 String AsString(const RGBA& c)
88 {
89 	return Format("RGBA(%d, %d, %d, %d)", (int)c.r, (int)c.g, (int)c.b, (int)c.a);
90 }
91 
operator RGBA() const92 Color::operator RGBA() const
93 {
94 	RGBA color;
95 	if(IsNullInstance())
96 		Zero(color);
97 	else {
98 		color.r = GetR();
99 		color.g = GetG();
100 		color.b = GetB();
101 		color.a = 255;
102 	}
103 	return color;
104 }
105 
Color(RGBA rgba)106 Color::Color(RGBA rgba)
107 {
108 	if(rgba.a == 0)
109 		color = 0xffffffff;
110 	else {
111 		if(rgba.a == 255)
112 			color = RGB(rgba.r, rgba.g, rgba.b);
113 		else {
114 			int alpha = 65536 / rgba.a;
115 			color = RGB((alpha * rgba.r) >> 8, (alpha * rgba.g) >> 8, (alpha * rgba.b) >> 8);
116 		}
117 	}
118 }
119 
Jsonize(JsonIO & jio)120 void Color::Jsonize(JsonIO& jio)
121 {
122 	int r, g, b;
123 	if(IsNullInstance()) {
124 		r = g = b = Null;
125 	}
126 	else {
127 		r = GetR();
128 		g = GetG();
129 		b = GetB();
130 	}
131 	jio("red", r)("green", g)("blue", b);
132 	if(IsNull(r))
133 		*this = Null;
134 	else
135 		*this = Color(r, g, b);
136 }
137 
Xmlize(XmlIO & xio)138 void Color::Xmlize(XmlIO& xio)
139 {
140 	int r, g, b;
141 	if(IsNullInstance()) {
142 		r = g = b = Null;
143 	}
144 	else {
145 		r = GetR();
146 		g = GetG();
147 		b = GetB();
148 	}
149 	xio
150 		.Attr("red", r)
151 		.Attr("green", g)
152 		.Attr("blue", b)
153 	;
154 	if(IsNull(r))
155 		*this = Null;
156 	else
157 		*this = Color(r, g, b);
158 }
159 
operator *(int alpha,Color c)160 RGBA operator*(int alpha, Color c)
161 {
162 	RGBA r;
163 	r.a = alpha;
164 	alpha += (alpha >> 7);
165 	r.r = (alpha * c.GetR()) >> 8;
166 	r.g = (alpha * c.GetG()) >> 8;
167 	r.b = (alpha * c.GetB()) >> 8;
168 	return r;
169 }
170 
171 template<>
AsString(const Color & c)172 String AsString(const Color& c) {
173 	if(IsNull(c))
174 		return "Color(Null)";
175 	if(c.GetRaw() & 0x80000000)
176 		return Format("Color(%d, 0)", int(c.GetRaw() & ~0x80000000));
177 	return Format("Color(%d, %d, %d)", c.GetR(), c.GetG(), c.GetB());
178 }
179 
ColorToHtml(Color color)180 String ColorToHtml(Color color)
181 {
182 	return IsNull(color) ? Null : Format("#%02X%02X%02X",  color.GetR(), color.GetG(), color.GetB());
183 }
184 
sCharFilterNoDigit(int c)185 static int sCharFilterNoDigit(int c)
186 {
187 	return IsDigit(c) ? 0 : c;
188 }
189 
sCharFilterHex(int c)190 static int sCharFilterHex(int c)
191 {
192 	return c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F' || IsDigit(c) ? c : 0;
193 }
194 
ColorFromText(const char * s)195 Color ColorFromText(const char *s)
196 {
197 	Vector<String> h = Split(s, sCharFilterNoDigit);
198 	if(h.GetCount() == 3 && (strchr(s, ',') || strchr(s, ';') || strchr(s, '.') || strchr(s, ' '))) {
199 		int r = atoi(h[0]);
200 		int g = atoi(h[1]);
201 		int b = atoi(h[2]);
202 		if(r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255)
203 			return Color(r, g, b);
204 	}
205 	String hex = Filter(s, sCharFilterHex);
206 	if(hex.GetCount() == 6 || hex.GetCount() == 8) {
207 		dword w = (dword)ScanInt64(~hex, NULL, 16);
208 		return Color(byte(w >> 16), byte(w >> 8), byte(w));
209 	}
210 	return Null;
211 }
212 
Blend(Color c1,Color c2,int alpha)213 Color Blend(Color c1, Color c2, int alpha)
214 {
215 	int a = (alpha >> 7) + alpha;
216 	return Color(min(((a * (c2.GetR() - c1.GetR())) >> 8) + c1.GetR(), 255),
217 	             min(((a * (c2.GetG() - c1.GetG())) >> 8) + c1.GetG(), 255),
218 	             min(((a * (c2.GetB() - c1.GetB())) >> 8) + c1.GetB(), 255));
219 }
220 
221 INITBLOCK {
222 	Value::SvoRegister<Color>("Color");
223 }
224 
225 int  Grayscale(const Color& c)
226 {
227 	return (77 * c.GetR() + 151 * c.GetG() + 28 * c.GetB()) >> 8;
228 }
229 
IsDark(Color c)230 bool IsDark(Color c)
231 {
232 	return Grayscale(c) < 80;
233 }
234 
IsLight(Color c)235 bool IsLight(Color c)
236 {
237 	return Grayscale(c) > 255 - 80;
238 }
239 
Grayscale2(const Color & c)240 int  Grayscale2(const Color& c)
241 {
242 	return (c.GetR() + c.GetG() + c.GetB()) / 3;
243 }
244 
DarkTheme(Color color)245 Color DarkTheme(Color color)
246 {
247 	if(IsNull(color))
248 		return Null;
249 
250 	double v[3];
251 	v[0] = color.GetR();
252 	v[1] = color.GetG();
253 	v[2] = color.GetB();
254 
255 // this represent physiological perception of brightness of R,G,B. Sum = 1
256 //	static double c[3] = { 0.21, 0.72, 0.07 }; // physiologically correct values
257 	static double c[3] = { 0.3, 0.5, 0.2 }; // with this set, blues and reds are more pronounced
258 
259 	double m0 = c[0] * v[0] + c[1] * v[1] + c[2] * v[2]; // base brightness
260 
261 	const int middle = 155; // this value represents gamma-like feature, imbalance of perception of dark vs bright
262 	const double up = (256.0 - middle) / middle;
263 	const double down = 1 / up;
264 
265 	double m;
266 	if(m0 < middle)
267 		m = middle + (middle - m0) * up;
268 	else
269 		m = middle - (m0 - middle) * down;
270 
271 	int i0 = 0;
272 	int i1 = 1;
273 	int i2 = 2;
274 
275 	if(v[i0] > v[i1])
276 		Swap(i0, i1);
277 	if(v[i1] > v[i2])
278 		Swap(i1, i2);
279 	if(v[i0] > v[i1])
280 		Swap(i0, i1);
281 
282 	if(m0 < m) {
283 		m -= m0;
284 		double a = min(v[i2] + m, 255.0) - v[i2];
285 		v[i0] += a;
286 		v[i1] += a;
287 		v[i2] += a;
288 		m -= a;
289 
290 		a = min(v[i1] + m / (c[i0] + c[i1]), 255.0) - v[i1];
291 		v[i0] += a;
292 		v[i1] += a;
293 		m -= (c[i0] + c[i1]) * a;
294 
295 		v[i0] = min(v[i0] + m / c[i1], 255.0);
296 	}
297 	else {
298 		m = m0 - m;
299 		double a = v[i0] - max(v[i0] - m, 0.0);
300 		v[i0] -= a;
301 		v[i1] -= a;
302 		v[i2] -= a;
303 		m -= a;
304 
305 		a = v[i1] - max(v[i1] - m / (c[i1] + c[i2]), 0.0);
306 		v[i1] -= a;
307 		v[i2] -= a;
308 		m -= (c[i1] + c[i2]) * a;
309 
310 		v[i2] = max(v[i2] - m / c[i2], 0.0);
311 	}
312 
313 	return Color((int)v[0], (int)v[1], (int)v[2]);
314 }
315 
DarkThemeCached(Color c)316 Color DarkThemeCached(Color c)
317 {
318 	const int N = 8;
319 	thread_local struct Cache {
320 		Color icolor[N];
321 		Color ocolor[N];
322 		int   ii = 0;
323 
324 		Cache() {
325 			for(int i = 0; i < N; i++) {
326 				icolor[i] = Color(0, 0, 0);
327 				ocolor[i] = Color(255, 255, 255);
328 			}
329 		}
330 	} cache;
331 	#define DO(i) if(cache.icolor[i] == c) return cache.ocolor[i];
332 	DO(0); DO(1); DO(2); DO(3); DO(4); DO(5); DO(6); DO(7);
333 	cache.ii = (cache.ii + 1) & (N - 1);
334 	cache.icolor[cache.ii] = c;
335 	c = DarkTheme(c);
336 	cache.ocolor[cache.ii] = c;
337 	return c;
338 }
339 
340 }
341