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> 
19using namespace tMath; 
20 
21 
22// Uses C++11 aggregate initialization syntax. 
23const tColouri tColouri::black = { 0x00, 0x00, 0x00 }; 
24const tColouri tColouri::white = { 0xFF, 0xFF, 0xFF }; 
25const tColouri tColouri::pink = { 0xFF, 0x80, 0x80 }; 
26const tColouri tColouri::red = { 0xFF, 0x00, 0x00 }; 
27const tColouri tColouri::green = { 0x00, 0xFF, 0x00 }; 
28const tColouri tColouri::blue = { 0x00, 0x00, 0xFF }; 
29const tColouri tColouri::grey = { 0x80, 0x80, 0x80 }; 
30const tColouri tColouri::lightgrey = { 0xC0, 0xC0, 0xC0 }; 
31const tColouri tColouri::darkgrey = { 0x40, 0x40, 0x40 }; 
32const tColouri tColouri::cyan = { 0x00, 0xFF, 0xFF }; 
33const tColouri tColouri::magenta = { 0xFF, 0x00, 0xFF }; 
34const tColouri tColouri::yellow = { 0xFF, 0xFF, 0x00 }; 
35const tColouri tColouri::transparent = { 0x00, 0x00, 0x00, 0x00 }; 
36 
37 
38const tColourf tColourf::invalid = { -1.0f, -1.0f, -1.0f, -1.0f }; 
39const tColourf tColourf::black = { 0.0f, 0.0f, 0.0f }; 
40const tColourf tColourf::white = { 1.0f, 1.0f, 1.0f }; 
41const tColourf tColourf::hotpink = { 1.0f, 0.5f, 0.5f }; 
42const tColourf tColourf::red = { 1.0f, 0.0f, 0.0f }; 
43const tColourf tColourf::green = { 0.0f, 1.0f, 0.0f }; 
44const tColourf tColourf::blue = { 0.0f, 0.0f, 1.0f }; 
45const tColourf tColourf::grey = { 0.5f, 0.5f, 0.5f }; 
46const tColourf tColourf::lightgrey = { 0.75f, 0.75f, 0.75f }; 
47const tColourf tColourf::darkgrey = { 0.25f, 0.25f, 0.25f }; 
48const tColourf tColourf::cyan = { 0.0f, 1.0f, 1.0f }; 
49const tColourf tColourf::magenta = { 1.0f, 0.0f, 1.0f }; 
50const tColourf tColourf::yellow = { 1.0f, 1.0f, 0.0f }; 
51const tColourf tColourf::transparent = { 0.0f, 0.0f, 0.0f, 0.0f }; 
52 
53 
54const tColour3f tColour3f::invalid = { -1.0f, -1.0f, -1.0f }; 
55const tColour3f tColour3f::black = { 0.0f, 0.0f, 0.0f }; 
56const tColour3f tColour3f::white = { 1.0f, 1.0f, 1.0f }; 
57const tColour3f tColour3f::hotpink = { 1.0f, 0.5f, 0.5f }; 
58const tColour3f tColour3f::red = { 1.0f, 0.0f, 0.0f }; 
59const tColour3f tColour3f::green = { 0.0f, 1.0f, 0.0f }; 
60const tColour3f tColour3f::blue = { 0.0f, 0.0f, 1.0f }; 
61const tColour3f tColour3f::grey = { 0.5f, 0.5f, 0.5f }; 
62const tColour3f tColour3f::lightgrey = { 0.75f, 0.75f, 0.75f }; 
63const tColour3f tColour3f::darkgrey = { 0.25f, 0.25f, 0.25f }; 
64const tColour3f tColour3f::cyan = { 0.0f, 1.0f, 1.0f }; 
65const tColour3f tColour3f::magenta = { 1.0f, 0.0f, 1.0f }; 
66const tColour3f tColour3f::yellow = { 1.0f, 1.0f, 0.0f }; 
67 
68 
69void 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 
109void 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 
194void 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 
247void 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 
330tColouri 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 
349float 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