1 | // tColour.cpp  |
2 | //  |
3 | // Colour and pixel classes. Both a 32 bit integral representation as well as a 4 component floating point one can be  |
4 | // found in this file.  |
5 | //  |
6 | // Copyright (c) 2006, 2011, 2017, 2020 Tristan Grimmer.  |
7 | // Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby  |
8 | // granted, provided that the above copyright notice and this permission notice appear in all copies.  |
9 | //  |
10 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL  |
11 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,  |
12 | // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN  |
13 | // AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR  |
14 | // PERFORMANCE OF THIS SOFTWARE.  |
15 |   |
16 | #include <Foundation/tString.h>  |
17 | #include <Foundation/tHash.h>  |
18 | #include <Math/tColour.h>  |
19 | using namespace tMath;  |
20 |   |
21 |   |
22 | // Uses C++11 aggregate initialization syntax.  |
23 | const tColouri tColouri::black = { 0x00, 0x00, 0x00 };  |
24 | const tColouri tColouri::white = { 0xFF, 0xFF, 0xFF };  |
25 | const tColouri tColouri::pink = { 0xFF, 0x80, 0x80 };  |
26 | const tColouri tColouri::red = { 0xFF, 0x00, 0x00 };  |
27 | const tColouri tColouri::green = { 0x00, 0xFF, 0x00 };  |
28 | const tColouri tColouri::blue = { 0x00, 0x00, 0xFF };  |
29 | const tColouri tColouri::grey = { 0x80, 0x80, 0x80 };  |
30 | const tColouri tColouri::lightgrey = { 0xC0, 0xC0, 0xC0 };  |
31 | const tColouri tColouri::darkgrey = { 0x40, 0x40, 0x40 };  |
32 | const tColouri tColouri::cyan = { 0x00, 0xFF, 0xFF };  |
33 | const tColouri tColouri::magenta = { 0xFF, 0x00, 0xFF };  |
34 | const tColouri tColouri::yellow = { 0xFF, 0xFF, 0x00 };  |
35 | const tColouri tColouri::transparent = { 0x00, 0x00, 0x00, 0x00 };  |
36 |   |
37 |   |
38 | const tColourf tColourf::invalid = { -1.0f, -1.0f, -1.0f, -1.0f };  |
39 | const tColourf tColourf::black = { 0.0f, 0.0f, 0.0f };  |
40 | const tColourf tColourf::white = { 1.0f, 1.0f, 1.0f };  |
41 | const tColourf tColourf::hotpink = { 1.0f, 0.5f, 0.5f };  |
42 | const tColourf tColourf::red = { 1.0f, 0.0f, 0.0f };  |
43 | const tColourf tColourf::green = { 0.0f, 1.0f, 0.0f };  |
44 | const tColourf tColourf::blue = { 0.0f, 0.0f, 1.0f };  |
45 | const tColourf tColourf::grey = { 0.5f, 0.5f, 0.5f };  |
46 | const tColourf tColourf::lightgrey = { 0.75f, 0.75f, 0.75f };  |
47 | const tColourf tColourf::darkgrey = { 0.25f, 0.25f, 0.25f };  |
48 | const tColourf tColourf::cyan = { 0.0f, 1.0f, 1.0f };  |
49 | const tColourf tColourf::magenta = { 1.0f, 0.0f, 1.0f };  |
50 | const tColourf tColourf::yellow = { 1.0f, 1.0f, 0.0f };  |
51 | const tColourf tColourf::transparent = { 0.0f, 0.0f, 0.0f, 0.0f };  |
52 |   |
53 |   |
54 | const tColour3f tColour3f::invalid = { -1.0f, -1.0f, -1.0f };  |
55 | const tColour3f tColour3f::black = { 0.0f, 0.0f, 0.0f };  |
56 | const tColour3f tColour3f::white = { 1.0f, 1.0f, 1.0f };  |
57 | const tColour3f tColour3f::hotpink = { 1.0f, 0.5f, 0.5f };  |
58 | const tColour3f tColour3f::red = { 1.0f, 0.0f, 0.0f };  |
59 | const tColour3f tColour3f::green = { 0.0f, 1.0f, 0.0f };  |
60 | const tColour3f tColour3f::blue = { 0.0f, 0.0f, 1.0f };  |
61 | const tColour3f tColour3f::grey = { 0.5f, 0.5f, 0.5f };  |
62 | const tColour3f tColour3f::lightgrey = { 0.75f, 0.75f, 0.75f };  |
63 | const tColour3f tColour3f::darkgrey = { 0.25f, 0.25f, 0.25f };  |
64 | const tColour3f tColour3f::cyan = { 0.0f, 1.0f, 1.0f };  |
65 | const tColour3f tColour3f::magenta = { 1.0f, 0.0f, 1.0f };  |
66 | const tColour3f tColour3f::yellow = { 1.0f, 1.0f, 0.0f };  |
67 |   |
68 |   |
69 | void tMath::tRGBToHSV(int& h, int& s, int& v, int r, int g, int b, tAngleMode angleMode)  |
70 | {  |
71 | double min = double( tMin(r, g, b) );  |
72 | double max = double( tMax(r, g, b) );  |
73 | double delta = max - min;  |
74 |   |
75 | v = int(max);  |
76 | if (!delta)  |
77 | {  |
78 | h = s = 0;  |
79 | return;  |
80 | }  |
81 |   |
82 | double temp = delta/max;  |
83 | s = int(temp*255.0);  |
84 |   |
85 | if (r == int(max))  |
86 | temp = double(g-b) / delta;  |
87 | else if (g == int(max))  |
88 | temp = 2.0 + double(b-r) / delta;  |
89 | else  |
90 | temp = 4.0 + double(r-g) / delta;  |
91 |   |
92 | // Compute hue in correct angle units.  |
93 | tAssert((angleMode == tAngleMode::Degrees) || (angleMode == tAngleMode::Norm256));  |
94 | double fullCircle = 360.0;  |
95 | if (angleMode == tAngleMode::Norm256)  |
96 | fullCircle = 256.0;  |
97 |   |
98 | temp *= fullCircle / 6.0;  |
99 | if (temp < 0.0)  |
100 | temp += fullCircle;  |
101 |   |
102 | if (temp >= fullCircle)  |
103 | temp = 0;  |
104 |   |
105 | h = int(temp);  |
106 | }  |
107 |   |
108 |   |
109 | void tMath::tHSVToRGB(int& r, int& g, int& b, int h, int s, int v, tAngleMode angleMode)  |
110 | {  |
111 | tAssert((angleMode == tAngleMode::Degrees) || (angleMode == tAngleMode::Norm256));  |
112 | int fullCircle = 360;  |
113 | if (angleMode == tAngleMode::Norm256)  |
114 | fullCircle = 256;  |
115 |   |
116 | while (h >= fullCircle)  |
117 | h -= fullCircle;  |
118 |   |
119 | while (h < 0)  |
120 | h += fullCircle;  |
121 |   |
122 | tiClamp(h, 0, fullCircle-1);  |
123 | tiClamp(s, 0, 255);  |
124 | tiClamp(v, 0, 255);  |
125 |   |
126 | if (!h && !s)  |
127 | {  |
128 | r = g = b = v;  |
129 | return;  |
130 | }  |
131 |   |
132 | double max = double(v);  |
133 | double delta = max*s / 255.0;  |
134 | double min = max - delta;  |
135 | double hue = double(h);  |
136 | double circle = double(fullCircle);  |
137 | double oneSixthCircle = circle/6.0; // 60 degrees.  |
138 | double oneThirdCircle = circle/3.0; // 120 degrees.  |
139 | double oneHalfCircle = circle/2.0; // 180 degrees.  |
140 | double twoThirdCircle = (2.0*circle)/3.0; // 240 degrees.  |
141 | double fiveSixthCircle = (5.0*circle)/6.0; // 300 degrees.  |
142 |   |
143 | if (h > fiveSixthCircle || h <= oneSixthCircle)  |
144 | {  |
145 | r = int(max);  |
146 | if (h > fiveSixthCircle)  |
147 | {  |
148 | g = int(min);  |
149 | hue = (hue - circle)/oneSixthCircle;  |
150 | b = int(min - hue*delta);  |
151 | }  |
152 | else  |
153 | {  |
154 | b = int(min);  |
155 | hue = hue / oneSixthCircle;  |
156 | g = int(hue*delta + min);  |
157 | }  |
158 | }  |
159 | else if (h > oneSixthCircle && h < oneHalfCircle)  |
160 | {  |
161 | g = int(max);  |
162 | if (h < oneThirdCircle)  |
163 | {  |
164 | b = int(min);  |
165 | hue = (hue/oneSixthCircle - 2.0) * delta;  |
166 | r = int(min - hue);  |
167 | }  |
168 | else  |
169 | {  |
170 | r = int(min);  |
171 | hue = (hue/oneSixthCircle - 2.0) * delta;  |
172 | b = int(min + hue);  |
173 | }  |
174 | }  |
175 | else  |
176 | {  |
177 | b = int(max);  |
178 | if (h < twoThirdCircle)  |
179 | {  |
180 | r = int(min);  |
181 | hue = (hue/oneSixthCircle - 4.0) * delta;  |
182 | g = int(min - hue);  |
183 | }  |
184 | else  |
185 | {  |
186 | g = int(min);  |
187 | hue = (hue/oneSixthCircle - 4.0) * delta;  |
188 | r = int(min + hue);  |
189 | }  |
190 | }  |
191 | }  |
192 |   |
193 |   |
194 | void tMath::tRGBToHSV(float& h, float& s, float& v, float r, float g, float b, tAngleMode angleMode)  |
195 | {  |
196 | float min = tMin(r, g, b);  |
197 | float max = tMax(r, g, b);  |
198 |   |
199 | v = max;  |
200 | float delta = max - min;  |
201 | if (max > 0.0f)  |
202 | {  |
203 | s = (delta / max);  |
204 | }  |
205 | else  |
206 | {  |
207 | // Max is 0 so we're black with v = 0.  |
208 | // Hue and Sat are irrelevant at this point but we zero them to be clean.  |
209 | s = 0.0f;  |
210 | h = 0.0f;  |
211 | }  |
212 |   |
213 | if (r >= max)  |
214 | h = (g - b) / delta; // Between yellow & magenta.  |
215 | else if (g >= max)  |
216 | h = 2.0f + (b - r) / delta; // Between cyan & yellow.  |
217 | else  |
218 | h = 4.0f + (r - g) / delta; // Between magenta & cyan.  |
219 |   |
220 | float fullCircle = 360.0f;  |
221 | switch (angleMode)  |
222 | {  |
223 | case tAngleMode::Radians:  |
224 | fullCircle = TwoPi;  |
225 | break;  |
226 |   |
227 | case tAngleMode::Degrees:  |
228 | fullCircle = 360.0f;  |
229 | break;  |
230 |   |
231 | case tAngleMode::Norm256:  |
232 | fullCircle = 256.0f;  |
233 | break;  |
234 |   |
235 | case tAngleMode::NormOne:  |
236 | fullCircle = 1.0f;  |
237 | break;  |
238 | }  |
239 |   |
240 | h *= fullCircle / 6.0f;  |
241 |   |
242 | if (h < 0.0f)  |
243 | h += fullCircle;  |
244 | }  |
245 |   |
246 |   |
247 | void tMath::tHSVToRGB(float& r, float& g, float& b, float h, float s, float v, tAngleMode angleMode)  |
248 | {  |
249 | // If sat is zero we always ignore the hue. That is, we're a shade of grey on the vertical line.  |
250 | if (s <= 0.0f)  |
251 | {  |
252 | r = v;  |
253 | g = v;  |
254 | b = v;  |
255 | return;  |
256 | }  |
257 |   |
258 | float fullCircle = 360.0f;  |
259 | switch (angleMode)  |
260 | {  |
261 | case tAngleMode::Radians:  |
262 | fullCircle = TwoPi;  |
263 | break;  |
264 |   |
265 | case tAngleMode::Degrees:  |
266 | fullCircle = 360.0f;  |
267 | break;  |
268 |   |
269 | case tAngleMode::Norm256:  |
270 | fullCircle = 256.0f;  |
271 | break;  |
272 |   |
273 | case tAngleMode::NormOne:  |
274 | fullCircle = 1.0f;  |
275 | break;  |
276 | }  |
277 |   |
278 | if (h >= fullCircle)  |
279 | h = 0.0f;  |
280 | h /= (fullCircle/6.0f);  |
281 |   |
282 | int i = int(h);  |
283 | float rem = h - i;  |
284 | float p = v * (1.0f - s);  |
285 | float q = v * (1.0f - (s * rem));  |
286 | float t = v * (1.0f - (s * (1.0f - rem)));  |
287 |   |
288 | switch (i)  |
289 | {  |
290 | case 0:  |
291 | r = v;  |
292 | g = t;  |
293 | b = p;  |
294 | break;  |
295 |   |
296 | case 1:  |
297 | r = q;  |
298 | g = v;  |
299 | b = p;  |
300 | break;  |
301 |   |
302 | case 2:  |
303 | r = p;  |
304 | g = v;  |
305 | b = t;  |
306 | break;  |
307 |   |
308 | case 3:  |
309 | r = p;  |
310 | g = q;  |
311 | b = v;  |
312 | break;  |
313 |   |
314 | case 4:  |
315 | r = t;  |
316 | g = p;  |
317 | b = v;  |
318 | break;  |
319 |   |
320 | case 5:  |
321 | default:  |
322 | r = v;  |
323 | g = p;  |
324 | b = q;  |
325 | break;  |
326 | }  |
327 | }  |
328 |   |
329 |   |
330 | tColouri tMath::tGetColour(const char* colourName)  |
331 | {  |
332 | tString lowerName(colourName);  |
333 | lowerName.ToLower();  |
334 | uint32 colourHash = tHash::tHashStringFast32(lowerName);  |
335 | tColouri colour = tColouri::white;  |
336 |   |
337 | // This switch uses compile-time hashes. Collisions will be automatically detected by the compiler.  |
338 | switch (colourHash)  |
339 | {  |
340 | case tHash::tHashCT("none" ): colour = 0xFFFFFFFF; break;  |
341 | case tHash::tHashCT("black" ): colour = 0x000000FF; break;  |
342 | default: break;  |
343 | }  |
344 |   |
345 | return colour;   |
346 | }  |
347 |   |
348 |   |
349 | float tMath::tColourDiffRedmean(const tColouri& aa, const tColouri& bb)  |
350 | {  |
351 | tVector3 a; aa.GetDenorm(a);  |
352 | tVector3 b; bb.GetDenorm(b);  |
353 |   |
354 | float rhat = (a.x + b.x) / 2.0f;  |
355 |   |
356 | float dR2 = tSquare(a.x - b.x);  |
357 | float dG2 = tSquare(a.y - b.y);  |
358 | float dB2 = tSquare(a.z - b.z);  |
359 |   |
360 | float term1 = (2.0f + rhat/256.0f)*dR2;  |
361 | float term2 = 4.0f * dG2;  |
362 | float term3 = (2.0f + ((255.0f-rhat)/256.0f)) * dB2;  |
363 |   |
364 | return tSqrt(term1 + term2 + term3);  |
365 | }  |
366 | |