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, 2022-2024 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 
22const char* tColourProfileNames[] = 
23
24 "LDRsRGB_LDRlA", // RGB is sRGB space. Alpha in linear. LDR (0.0 to 1.0) for both. 
25 "LDRgRGB_LDRlA"
26 "LDRlRGBA"
27 "HDRlRGB_LDRlA"
28 "HDRlRGBA"
29 "Auto" 
30}; 
31tStaticAssert(tNumElements(tColourProfileNames) == int(tColourProfile::NumProfiles)); 
32 
33 
34const char* tColourProfileShortNames[] = 
35
36 "sRGB"
37 "gRGB"
38 "lRGB"
39 "HDRa"
40 "HDRA"
41 "Auto" 
42}; 
43tStaticAssert(tNumElements(tColourProfileShortNames) == int(tColourProfile::NumProfiles)); 
44 
45 
46const char* tGetColourProfileName(tColourProfile profile
47
48 if (profile == tColourProfile::Unspecified) return "Unspecified"
49 return tColourProfileNames[int(profile)]; 
50
51 
52 
53const char* tGetColourProfileShortName(tColourProfile profile
54
55 if (profile == tColourProfile::None) return "None"
56 return tColourProfileShortNames[int(profile)]; 
57
58 
59 
60const char* tAlphaModeNames[] = 
61
62 "Normal"
63 "PreMult" 
64}; 
65tStaticAssert(tNumElements(tAlphaModeNames) == int(tAlphaMode::NumModes)); 
66 
67 
68const char* tAlphaModeShortNames[] = 
69
70 "Norm"
71 "Mult" 
72}; 
73tStaticAssert(tNumElements(tAlphaModeShortNames) == int(tAlphaMode::NumModes)); 
74 
75 
76const char* tGetAlphaModeName(tAlphaMode mode
77
78 if (mode == tAlphaMode::Unspecified) return "Unspecified"
79 return tAlphaModeNames[int(mode)]; 
80
81 
82 
83const char* tGetAlphaModeShortName(tAlphaMode mode
84
85 if (mode == tAlphaMode::None) return "None"
86 return tAlphaModeShortNames[int(mode)]; 
87
88 
89 
90const char* tChannelTypeNames[] = 
91
92 "UnsignedIntNormalized"
93 "SignedIntNormalized"
94 "UnsignedInt"
95 "SignedInt"
96 "UnsignedFloat"
97 "SignedFloat" 
98}; 
99tStaticAssert(tNumElements(tChannelTypeNames) == int(tChannelType::NumTypes)); 
100 
101 
102const char* tChannelTypeShortNames[] = 
103
104 "UNORM"
105 "SNORM"
106 "UINT"
107 "SINT"
108 "UFLOAT"
109 "SFLOAT" 
110}; 
111tStaticAssert(tNumElements(tChannelTypeShortNames) == int(tChannelType::NumTypes)); 
112 
113 
114const char* tGetChannelTypeName(tChannelType type
115
116 if (type == tChannelType::Unspecified) return "Unspecified"
117 return tChannelTypeNames[int(type)]; 
118
119 
120 
121const char* tGetChannelTypeShortName(tChannelType type
122
123 if (type == tChannelType::NONE) return "NONE"
124 return tChannelTypeShortNames[int(type)]; 
125
126 
127 
128tChannelType tGetChannelType(const char* name
129
130 if (!name || (name[0] == '\0')) 
131 return tChannelType::Invalid
132 
133 for (int t = 0; t < int(tChannelType::NumTypes); t++) 
134 if (tStd::tStrcmp(a: tChannelTypeShortNames[t], b: name) == 0
135 return tChannelType(t); 
136 
137 for (int t = 0; t < int(tChannelType::NumTypes); t++) 
138 if (tStd::tStrcmp(a: tChannelTypeNames[t], b: name) == 0
139 return tChannelType(t); 
140 
141 return tChannelType::Invalid
142
143 
144 
145// Uses C++11 aggregate initialization syntax. 
146const tColour4b tColour4b::black = { 0x00, 0x00, 0x00, 0xFF }; 
147const tColour4b tColour4b::white = { 0xFF, 0xFF, 0xFF, 0xFF }; 
148const tColour4b tColour4b::pink = { 0xFF, 0x80, 0x80, 0xFF }; 
149const tColour4b tColour4b::red = { 0xFF, 0x00, 0x00, 0xFF }; 
150const tColour4b tColour4b::green = { 0x00, 0xFF, 0x00, 0xFF }; 
151const tColour4b tColour4b::blue = { 0x00, 0x00, 0xFF, 0xFF }; 
152const tColour4b tColour4b::grey = { 0x80, 0x80, 0x80, 0xFF }; 
153const tColour4b tColour4b::lightgrey = { 0xC0, 0xC0, 0xC0, 0xFF }; 
154const tColour4b tColour4b::darkgrey = { 0x40, 0x40, 0x40, 0xFF }; 
155const tColour4b tColour4b::cyan = { 0x00, 0xFF, 0xFF, 0xFF }; 
156const tColour4b tColour4b::magenta = { 0xFF, 0x00, 0xFF, 0xFF }; 
157const tColour4b tColour4b::yellow = { 0xFF, 0xFF, 0x00, 0xFF }; 
158const tColour4b tColour4b::transparent = { 0x00, 0x00, 0x00, 0x00 }; 
159 
160 
161const tColour4s tColour4s::black = { 0x0000, 0x0000, 0x0000, 0xFFFF }; 
162const tColour4s tColour4s::white = { 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF }; 
163const tColour4s tColour4s::pink = { 0xFFFF, 0x8000, 0x8000, 0xFFFF }; 
164const tColour4s tColour4s::red = { 0xFFFF, 0x0000, 0x0000, 0xFFFF }; 
165const tColour4s tColour4s::green = { 0x0000, 0xFFFF, 0x0000, 0xFFFF }; 
166const tColour4s tColour4s::blue = { 0x0000, 0x0000, 0xFFFF, 0xFFFF }; 
167const tColour4s tColour4s::grey = { 0x8000, 0x8000, 0x8000, 0xFFFF }; 
168const tColour4s tColour4s::lightgrey = { 0xC000, 0xC000, 0xC000, 0xFFFF }; 
169const tColour4s tColour4s::darkgrey = { 0x4000, 0x4000, 0x4000, 0xFFFF }; 
170const tColour4s tColour4s::cyan = { 0x0000, 0xFFFF, 0xFFFF, 0xFFFF }; 
171const tColour4s tColour4s::magenta = { 0xFFFF, 0x0000, 0xFFFF, 0xFFFF }; 
172const tColour4s tColour4s::yellow = { 0xFFFF, 0xFFFF, 0x0000, 0xFFFF }; 
173const tColour4s tColour4s::transparent = { 0x0000, 0x0000, 0x0000, 0x0000 }; 
174 
175 
176const tColour3f tColour3f::invalid = { -1.0f, -1.0f, -1.0f }; 
177const tColour3f tColour3f::black = { 0.00f, 0.00f, 0.00f }; 
178const tColour3f tColour3f::white = { 1.00f, 1.00f, 1.00f }; 
179const tColour3f tColour3f::hotpink = { 1.00f, 0.50f, 0.50f }; 
180const tColour3f tColour3f::red = { 1.00f, 0.00f, 0.00f }; 
181const tColour3f tColour3f::green = { 0.00f, 1.00f, 0.00f }; 
182const tColour3f tColour3f::blue = { 0.00f, 0.00f, 1.00f }; 
183const tColour3f tColour3f::grey = { 0.50f, 0.50f, 0.50f }; 
184const tColour3f tColour3f::lightgrey = { 0.75f, 0.75f, 0.75f }; 
185const tColour3f tColour3f::darkgrey = { 0.25f, 0.25f, 0.25f }; 
186const tColour3f tColour3f::cyan = { 0.00f, 1.00f, 1.00f }; 
187const tColour3f tColour3f::magenta = { 1.00f, 0.00f, 1.00f }; 
188const tColour3f tColour3f::yellow = { 1.00f, 1.00f, 0.00f }; 
189 
190 
191const tColour4f tColour4f::invalid = { -1.0f, -1.0f, -1.0f, -1.0f }; 
192const tColour4f tColour4f::black = { 0.00f, 0.00f, 0.00f, 1.00f }; 
193const tColour4f tColour4f::white = { 1.00f, 1.00f, 1.00f, 1.00f }; 
194const tColour4f tColour4f::hotpink = { 1.00f, 0.50f, 0.50f, 1.00f }; 
195const tColour4f tColour4f::red = { 1.00f, 0.00f, 0.00f, 1.00f }; 
196const tColour4f tColour4f::green = { 0.00f, 1.00f, 0.00f, 1.00f }; 
197const tColour4f tColour4f::blue = { 0.00f, 0.00f, 1.00f, 1.00f }; 
198const tColour4f tColour4f::grey = { 0.50f, 0.50f, 0.50f, 1.00f }; 
199const tColour4f tColour4f::lightgrey = { 0.75f, 0.75f, 0.75f, 1.00f }; 
200const tColour4f tColour4f::darkgrey = { 0.25f, 0.25f, 0.25f, 1.00f }; 
201const tColour4f tColour4f::cyan = { 0.00f, 1.00f, 1.00f, 1.00f }; 
202const tColour4f tColour4f::magenta = { 1.00f, 0.00f, 1.00f, 1.00f }; 
203const tColour4f tColour4f::yellow = { 1.00f, 1.00f, 0.00f, 1.00f }; 
204const tColour4f tColour4f::transparent = { 0.00f, 0.00f, 0.00f, 0.00f }; 
205 
206 
207void tMath::tRGBToHSV(int& h, int& s, int& v, int r, int g, int b, tAngleMode angleMode
208
209 double min = double( tMin(a: r, b: g, c: b) ); 
210 double max = double( tMax(a: r, b: g, c: b) ); 
211 double delta = max - min
212 
213 v = int(max); 
214 if (!delta
215
216 h = s = 0
217 return
218
219 
220 double temp = delta/max
221 s = int(temp*255.0); 
222 
223 if (r == int(max)) 
224 temp = double(g-b) / delta
225 else if (g == int(max)) 
226 temp = 2.0 + double(b-r) / delta
227 else 
228 temp = 4.0 + double(r-g) / delta
229 
230 // Compute hue in correct angle units. 
231 tAssert((angleMode == tAngleMode::Degrees) || (angleMode == tAngleMode::Norm256)); 
232 double fullCircle = 360.0
233 if (angleMode == tAngleMode::Norm256
234 fullCircle = 256.0
235 
236 temp *= fullCircle / 6.0
237 if (temp < 0.0
238 temp += fullCircle
239 
240 if (temp >= fullCircle
241 temp = 0
242 
243 h = int(temp); 
244
245 
246 
247void tMath::tHSVToRGB(int& r, int& g, int& b, int h, int s, int v, tAngleMode angleMode
248
249 tAssert((angleMode == tAngleMode::Degrees) || (angleMode == tAngleMode::Norm256)); 
250 int fullCircle = 360
251 if (angleMode == tAngleMode::Norm256
252 fullCircle = 256
253 
254 while (h >= fullCircle
255 h -= fullCircle
256 
257 while (h < 0
258 h += fullCircle
259 
260 tiClamp(val&: h, min: 0, max: fullCircle-1); 
261 tiClamp(val&: s, min: 0, max: 255); 
262 tiClamp(val&: v, min: 0, max: 255); 
263 
264 if (!h && !s
265
266 r = g = b = v
267 return
268
269 
270 double max = double(v); 
271 double delta = max*s / 255.0
272 double min = max - delta
273 double hue = double(h); 
274 double circle = double(fullCircle); 
275 double oneSixthCircle = circle/6.0; // 60 degrees. 
276 double oneThirdCircle = circle/3.0; // 120 degrees. 
277 double oneHalfCircle = circle/2.0; // 180 degrees. 
278 double twoThirdCircle = (2.0*circle)/3.0; // 240 degrees. 
279 double fiveSixthCircle = (5.0*circle)/6.0; // 300 degrees. 
280 
281 if (h > fiveSixthCircle || h <= oneSixthCircle
282
283 r = int(max); 
284 if (h > fiveSixthCircle
285
286 g = int(min); 
287 hue = (hue - circle)/oneSixthCircle
288 b = int(min - hue*delta); 
289
290 else 
291
292 b = int(min); 
293 hue = hue / oneSixthCircle
294 g = int(hue*delta + min); 
295
296
297 else if (h > oneSixthCircle && h < oneHalfCircle
298
299 g = int(max); 
300 if (h < oneThirdCircle
301
302 b = int(min); 
303 hue = (hue/oneSixthCircle - 2.0) * delta
304 r = int(min - hue); 
305
306 else 
307
308 r = int(min); 
309 hue = (hue/oneSixthCircle - 2.0) * delta
310 b = int(min + hue); 
311
312
313 else 
314
315 b = int(max); 
316 if (h < twoThirdCircle
317
318 r = int(min); 
319 hue = (hue/oneSixthCircle - 4.0) * delta
320 g = int(min - hue); 
321
322 else 
323
324 g = int(min); 
325 hue = (hue/oneSixthCircle - 4.0) * delta
326 r = int(min + hue); 
327
328
329
330 
331 
332void tMath::tRGBToHSV(float& h, float& s, float& v, float r, float g, float b, tAngleMode angleMode
333
334 float min = tMin(a: r, b: g, c: b); 
335 float max = tMax(a: r, b: g, c: b); 
336 
337 v = max
338 float delta = max - min
339 if (max > 0.0f
340
341 s = (delta / max); 
342
343 else 
344
345 // Max is 0 so we're black with v = 0. 
346 // Hue and Sat are irrelevant at this point but we zero them to be clean. 
347 s = 0.0f
348 h = 0.0f
349
350 
351 if (r >= max
352 h = (g - b) / delta; // Between yellow & magenta. 
353 else if (g >= max
354 h = 2.0f + (b - r) / delta; // Between cyan & yellow. 
355 else 
356 h = 4.0f + (r - g) / delta; // Between magenta & cyan. 
357 
358 float fullCircle = 360.0f
359 switch (angleMode
360
361 case tAngleMode::Radians: fullCircle = TwoPi; break
362 case tAngleMode::Degrees: fullCircle = 360.0f; break
363 case tAngleMode::Norm256: fullCircle = 256.0f; break
364 case tAngleMode::NormOne: fullCircle = 1.0f; break
365
366 
367 h *= fullCircle / 6.0f
368 if (h < 0.0f
369 h += fullCircle
370
371 
372 
373void tMath::tHSVToRGB(float& r, float& g, float& b, float h, float s, float v, tAngleMode angleMode
374
375 // If sat is zero we always ignore the hue. That is, we're a shade of grey on the vertical line. 
376 if (s <= 0.0f
377
378 r = v; g = v; b = v
379 return
380
381 
382 float fullCircle = 360.0f
383 switch (angleMode
384
385 case tAngleMode::Radians: fullCircle = TwoPi; break
386 case tAngleMode::Degrees: fullCircle = 360.0f; break
387 case tAngleMode::Norm256: fullCircle = 256.0f; break
388 case tAngleMode::NormOne: fullCircle = 1.0f; break
389
390 
391 if (h >= fullCircle
392 h = 0.0f
393 h /= (fullCircle/6.0f); 
394 
395 int i = int(h); 
396 float rem = h - i
397 float p = v * (1.0f - s); 
398 float q = v * (1.0f - (s * rem)); 
399 float t = v * (1.0f - (s * (1.0f - rem))); 
400 
401 switch (i
402
403 case 0: r = v; g = t; b = p; break
404 case 1: r = q; g = v; b = p; break
405 case 2: r = p; g = v; b = t; break
406 case 3: r = p; g = q; b = v; break
407 case 4: r = t; g = p; b = v; break
408 case 5
409 default: r = v; g = p; b = q; break
410
411
412 
413 
414tColour4b tMath::tGetColour(const char* colourName
415
416 tString lowerName(colourName); 
417 lowerName.ToLower(); 
418 uint32 colourHash = tHash::tHashStringFast32(s: lowerName); 
419 tColour4b colour = tColour4b::white
420 
421 // This switch uses compile-time hashes. Collisions will be automatically detected by the compiler. 
422 switch (colourHash
423
424 case tHash::tHashCT(s: "none"): colour = 0xFFFFFFFF; break
425 case tHash::tHashCT(s: "black"): colour = 0x000000FF; break
426 default: break
427
428 
429 return colour
430
431 
432 
433float tMath::tColourDiffRedmean(const tColour3b& aa, const tColour3b& bb
434
435 tVector3 a; aa.GetDenorm(dest&: a); 
436 tVector3 b; bb.GetDenorm(dest&: b); 
437 
438 float rhat = (a.x + b.x) / 2.0f
439 
440 float dR2 = tSquare(v: a.x - b.x); 
441 float dG2 = tSquare(v: a.y - b.y); 
442 float dB2 = tSquare(v: a.z - b.z); 
443 
444 float term1 = (2.0f + rhat/256.0f)*dR2
445 float term2 = 4.0f * dG2
446 float term3 = (2.0f + ((255.0f-rhat)/256.0f)) * dB2
447 
448 return tSqrt(x: term1 + term2 + term3); 
449
450 
451 
452float tMath::tColourDiffRedmean(const tColour4b& aa, const tColour4b& bb
453
454 tVector3 a; aa.GetDenorm(dest&: a); 
455 tVector3 b; bb.GetDenorm(dest&: b); 
456 
457 float rhat = (a.x + b.x) / 2.0f
458 
459 float dR2 = tSquare(v: a.x - b.x); 
460 float dG2 = tSquare(v: a.y - b.y); 
461 float dB2 = tSquare(v: a.z - b.z); 
462 
463 float term1 = (2.0f + rhat/256.0f)*dR2
464 float term2 = 4.0f * dG2
465 float term3 = (2.0f + ((255.0f-rhat)/256.0f)) * dB2
466 
467 return tSqrt(x: term1 + term2 + term3); 
468
469