QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskHctColor.cpp
1#include "QskHctColor.h"
2
3#include <qmath.h>
4#include <cmath>
5
6/*
7 Code has been copied, rearranged and translated to C++ without trying
8 to understand the implemented algorithms. All credits go to the authors of
9 https://github.com/material-foundation/material-color-utilities.
10 */
11
12/*
13 Copyright 2021 Google LLC
14
15 Licensed under the Apache License, Version 2.0 (the "License");
16 you may not use this file except in compliance with the License.
17 You may obtain a copy of the License at
18
19 http://www.apache.org/licenses/LICENSE-2.0
20
21 Unless required by applicable law or agreed to in writing, software
22 distributed under the License is distributed on an "AS IS" BASIS,
23 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24 See the License for the specific language governing permissions and
25 limitations under the License.
26 */
27
28namespace
29{
30 class XYZ
31 {
32 public:
33 constexpr XYZ() noexcept = default;
34
35 constexpr XYZ( double x, double y, double z ) noexcept
36 : x( x )
37 , y( y )
38 , z( z )
39 {
40 }
41
42 double value( int axis ) const
43 {
44 switch( axis )
45 {
46 case 0:
47 return x;
48 case 1:
49 return y;
50 case 2:
51 return z;
52 }
53
54 return 0.0;
55 }
56
57 double x = 0.0;
58 double y = 0.0;
59 double z = 0.0;
60 };
61
62 class ViewingConditions
63 {
64 public:
65
66 const double backgroundYTowhitePointY = 0.18418651851244416;
67
68 const double aw = 29.980997194447337;
69 const double nbb = 1.0169191804458755;
70 const double z = 1.909169568483652;
71 const double fl = 0.38848145378003529;
72
73 const XYZ rgbD = { 1.02117770275752, 0.98630772942801237, 0.93396050828022992 };
74 };
75}
76
77static const XYZ Y_FROM_LINRGB = { 0.2126, 0.7152, 0.0722 };
78
79static double planes[] =
80{
81 0.015176349177441876,
82 0.045529047532325624,
83 0.07588174588720938,
84 0.10623444424209313,
85 0.13658714259697685,
86 0.16693984095186062,
87 0.19729253930674434,
88 0.2276452376616281,
89 0.2579979360165119,
90 0.28835063437139563,
91 0.3188300904430532,
92 0.350925934958123,
93 0.3848314933096426,
94 0.42057480301049466,
95 0.458183274052838,
96 0.4976837250274023,
97 0.5391024159806381,
98 0.5824650784040898,
99 0.6277969426914107,
100 0.6751227633498623,
101 0.7244668422128921,
102 0.775853049866786,
103 0.829304845476233,
104 0.8848452951698498,
105 0.942497089126609,
106 1.0022825574869039,
107 1.0642236851973577,
108 1.1283421258858297,
109 1.1946592148522128,
110 1.2631959812511864,
111 1.3339731595349034,
112 1.407011200216447,
113 1.4823302800086415,
114 1.5599503113873272,
115 1.6398909516233677,
116 1.7221716113234105,
117 1.8068114625156377,
118 1.8938294463134073,
119 1.9832442801866852,
120 2.075074464868551,
121 2.1693382909216234,
122 2.2660538449872063,
123 2.36523901573795,
124 2.4669114995532007,
125 2.5710888059345764,
126 2.6777882626779785,
127 2.7870270208169257,
128 2.898822059350997,
129 3.0131901897720907,
130 3.1301480604002863,
131 3.2497121605402226,
132 3.3718988244681087,
133 3.4967242352587946,
134 3.624204428461639,
135 3.754355295633311,
136 3.887192587735158,
137 4.022731918402185,
138 4.160988767090289,
139 4.301978482107941,
140 4.445716283538092,
141 4.592217266055746,
142 4.741496401646282,
143 4.893568542229298,
144 5.048448422192488,
145 5.20615066083972,
146 5.3666897647573375,
147 5.5300801301023865,
148 5.696336044816294,
149 5.865471690767354,
150 6.037501145825082,
151 6.212438385869475,
152 6.390297286737924,
153 6.571091626112461,
154 6.7548350853498045,
155 6.941541251256611,
156 7.131223617812143,
157 7.323895587840543,
158 7.5195704746346665,
159 7.7182615035334345,
160 7.919981813454504,
161 8.124744458384042,
162 8.332562408825165,
163 8.543448553206703,
164 8.757415699253682,
165 8.974476575321063,
166 9.194643831691977,
167 9.417930041841839,
168 9.644347703669503,
169 9.873909240696694,
170 10.106627003236781,
171 10.342513269534024,
172 10.58158024687427,
173 10.8238400726681,
174 11.069304815507364,
175 11.317986476196008,
176 11.569896988756009,
177 11.825048221409341,
178 12.083451977536606,
179 12.345119996613247,
180 12.610063955123938,
181 12.878295467455942,
182 13.149826086772048,
183 13.42466730586372,
184 13.702830557985108,
185 13.984327217668513,
186 14.269168601521828,
187 14.55736596900856,
188 14.848930523210871,
189 15.143873411576273,
190 15.44220572664832,
191 15.743938506781891,
192 16.04908273684337,
193 16.35764934889634,
194 16.66964922287304,
195 16.985093187232053,
196 17.30399201960269,
197 17.62635644741625,
198 17.95219714852476,
199 18.281524751807332,
200 18.614349837764564,
201 18.95068293910138,
202 19.290534541298456,
203 19.633915083172692,
204 19.98083495742689,
205 20.331304511189067,
206 20.685334046541502,
207 21.042933821039977,
208 21.404114048223256,
209 21.76888489811322,
210 22.137256497705877,
211 22.50923893145328,
212 22.884842241736916,
213 23.264076429332462,
214 23.6469514538663,
215 24.033477234264016,
216 24.42366364919083,
217 24.817520537484558,
218 25.21505769858089,
219 25.61628489293138,
220 26.021211842414342,
221 26.429848230738664,
222 26.842203703840827,
223 27.258287870275353,
224 27.678110301598522,
225 28.10168053274597,
226 28.529008062403893,
227 28.96010235337422,
228 29.39497283293396,
229 29.83362889318845,
230 30.276079891419332,
231 30.722335150426627,
232 31.172403958865512,
233 31.62629557157785,
234 32.08401920991837,
235 32.54558406207592,
236 33.010999283389665,
237 33.4802739966603,
238 33.953417292456834,
239 34.430438229418264,
240 34.911345834551085,
241 35.39614910352207,
242 35.88485700094671,
243 36.37747846067349,
244 36.87402238606382,
245 37.37449765026789,
246 37.87891309649659,
247 38.38727753828926,
248 38.89959975977785,
249 39.41588851594697,
250 39.93615253289054,
251 40.460400508064545,
252 40.98864111053629,
253 41.520882981230194,
254 42.05713473317016,
255 42.597404951718396,
256 43.141702194811224,
257 43.6900349931913,
258 44.24241185063697,
259 44.798841244188324,
260 45.35933162437017,
261 45.92389141541209,
262 46.49252901546552,
263 47.065252796817916,
264 47.64207110610409,
265 48.22299226451468,
266 48.808024568002054,
267 49.3971762874833,
268 49.9904556690408,
269 50.587870934119984,
270 51.189430279724725,
271 51.79514187861014,
272 52.40501387947288,
273 53.0190544071392,
274 53.637271562750364,
275 54.259673423945976,
276 54.88626804504493,
277 55.517063457223934,
278 56.15206766869424,
279 56.79128866487574,
280 57.43473440856916,
281 58.08241284012621,
282 58.734331877617365,
283 59.39049941699807,
284 60.05092333227251,
285 60.715611475655585,
286 61.38457167773311,
287 62.057811747619894,
288 62.7353394731159,
289 63.417162620860914,
290 64.10328893648692,
291 64.79372614476921,
292 65.48848194977529,
293 66.18756403501224,
294 66.89098006357258,
295 67.59873767827808,
296 68.31084450182222,
297 69.02730813691093,
298 69.74813616640164,
299 70.47333615344107,
300 71.20291564160104,
301 71.93688215501312,
302 72.67524319850172,
303 73.41800625771542,
304 74.16517879925733,
305 74.9167682708136,
306 75.67278210128072,
307 76.43322770089146,
308 77.1981124613393,
309 77.96744375590167,
310 78.74122893956174,
311 79.51947534912904,
312 80.30219030335869,
313 81.08938110306934,
314 81.88105503125999,
315 82.67721935322541,
316 83.4778813166706,
317 84.28304815182372,
318 85.09272707154808,
319 85.90692527145302,
320 86.72564993000343,
321 87.54890820862819,
322 88.3767072518277,
323 89.2090541872801,
324 90.04595612594655,
325 90.88742016217518,
326 91.73345337380438,
327 92.58406282226491,
328 93.43925555268066,
329 94.29903859396902,
330 95.16341895893969,
331 96.03240364439274,
332 96.9059996312159,
333 97.78421388448044,
334 98.6670533535366,
335 99.55452497210776,
336};
337
338static inline int delinearized( double rgbComponent )
339{
340 const double normalized = rgbComponent / 100.0;
341
342 double v = 0.0;
343
344 if ( normalized <= 0.0031308 )
345 v = normalized * 12.92;
346 else
347 v = 1.055 * pow( normalized, 1.0 / 2.4 ) - 0.055;
348
349 return qBound( 0, qRound( v * 255 ), 255 );
350}
351
352static inline double labF( double t )
353{
354 constexpr double e = 216.0 / 24389.0;
355
356 if ( t > e )
357 {
358 return pow( t, 1.0 / 3.0 );
359 }
360 else
361 {
362 constexpr double kappa = 24389.0 / 27.0;
363 return ( kappa * t + 16 ) / 116;
364 }
365}
366
367static inline double labInvf( double ft )
368{
369 const double e = 216.0 / 24389.0;
370 const double kappa = 24389.0 / 27.0;
371 const double ft3 = ft * ft * ft;
372
373 if ( ft3 > e )
374 return ft3;
375
376 return ( 116 * ft - 16 ) / kappa;
377}
378
379static inline double sanitizeDegreesDouble( double degrees )
380{
381 degrees = fmod( degrees, 360.0 );
382
383 if (degrees < 0)
384 degrees = degrees + 360.0;
385
386 return degrees;
387}
388
389static inline double yFromLstar( double lstar )
390{
391 return 100.0 * labInvf( ( lstar + 16.0 ) / 116.0 );
392}
393
394static inline QRgb argbFromLstar( double lstar )
395{
396 const double y = yFromLstar(lstar);
397 const int component = delinearized(y);
398
399 return qRgb( component, component, component );
400}
401
402static inline QRgb argbFromLinrgb( const XYZ& linrgb )
403{
404 const int r = delinearized( linrgb.x );
405 const int g = delinearized( linrgb.y );
406 const int b = delinearized( linrgb.z );
407
408 return qRgb( r, g, b );
409}
410
411static inline double sanitizeRadians( double angle )
412{
413 return fmod( angle + M_PI * 8, M_PI * 2 );
414}
415
416static inline double trueDelinearized( double rgbComponent )
417{
418 const double normalized = rgbComponent / 100.0;
419 double v = 0.0;
420
421 if ( normalized <= 0.0031308 )
422 v = normalized * 12.92;
423 else
424 v = 1.055 * pow( normalized, 1.0 / 2.4 ) - 0.055;
425
426 return v * 255.0;
427}
428
429static inline int signum( double num )
430{
431 if (num == 0)
432 return 0;
433
434 return ( num < 0 ) ? -1 : 1;
435}
436
437static inline XYZ matrixMultiply( const XYZ& row, const XYZ matrix[3] )
438{
439 XYZ r;
440
441 r.x = row.x * matrix[0].x + row.y * matrix[0].y + row.z * matrix[0].z;
442 r.y = row.x * matrix[1].x + row.y * matrix[1].y + row.z * matrix[1].z;
443 r.z = row.x * matrix[2].x + row.y * matrix[2].y + row.z * matrix[2].z;
444
445 return r;
446}
447
448static inline double chromaticAdaptation( double component )
449{
450 const double af = pow( abs( component ), 0.42);
451 return signum(component) * 400.0 * af / ( af + 27.13 );
452}
453
454static inline double hueOf( const XYZ& linrgb )
455{
456 constexpr XYZ matrix[3] =
457 {
458 { 0.001200833568784504, 0.002389694492170889, 0.0002795742885861124 },
459 { 0.0005891086651375999, 0.0029785502573438758, 0.0003270666104008398 },
460 { 0.00010146692491640572, 0.0005364214359186694, 0.0032979401770712076 }
461 };
462
463 const XYZ scaledDiscount = matrixMultiply( linrgb, matrix );
464
465 const double rA = chromaticAdaptation( scaledDiscount.x );
466 const double gA = chromaticAdaptation( scaledDiscount.y );
467 const double bA = chromaticAdaptation( scaledDiscount.z );
468
469 const double a = ( 11.0 * rA + -12.0 * gA + bA ) / 11.0;
470 const double b = ( rA + gA - 2.0 * bA ) / 9.0;
471
472 return atan2( b, a );
473}
474
475static bool areInCyclicOrder( double a, double b, double c )
476{
477 const double deltaAB = sanitizeRadians( b - a );
478 const double deltaAC = sanitizeRadians( c - a );
479
480 return deltaAB < deltaAC;
481}
482
483static inline double intercept( double source, double mid, double target )
484{
485 return ( mid - source ) / ( target - source );
486}
487
488static inline XYZ setCoordinate( const XYZ& source,
489 double coordinate, const XYZ& target, int axis )
490{
491 const double t = intercept( source.value(axis), coordinate, target.value(axis) );
492
493 XYZ r;
494
495 r.x = source.x + ( target.x - source.x ) * t;
496 r.y = source.y + ( target.y - source.y ) * t;
497 r.z = source.z + ( target.z - source.z ) * t;
498
499 return r;
500}
501
502static bool isBounded( double x )
503{
504 return 0.0 <= x && x <= 100.0;
505}
506
507static XYZ nthVertex( double y, int n )
508{
509 const double kR = Y_FROM_LINRGB.x;
510 const double kG = Y_FROM_LINRGB.y;
511 const double kB = Y_FROM_LINRGB.z;
512
513 const double coordA = ( n % 4 <= 1 ) ? 0.0 : 100.0;
514 const double coordB = ( n % 2 == 0 ) ? 0.0 : 100.0;
515
516 if ( n < 4 )
517 {
518 const double g = coordA;
519 const double b = coordB;
520 const double r = ( y - g * kG - b * kB ) / kR;
521
522 if ( isBounded(r) )
523 return XYZ( r, g, b );
524 }
525 else if ( n < 8 )
526 {
527 const double b = coordA;
528 const double r = coordB;
529 const double g = ( y - r * kR - b * kB ) / kG;
530
531 if ( isBounded(g) )
532 return XYZ( r, g, b );
533 }
534 else
535 {
536 const double r = coordA;
537 const double g = coordB;
538 const double b = ( y - r * kR - g * kG ) / kB;
539
540 if ( isBounded(b) )
541 return XYZ( r, g, b );
542 }
543
544 return { -1.0, -1.0, -1.0 };
545}
546
547static void bisectToSegment( double y, double targetHue, XYZ& left, XYZ& right )
548{
549 left = { -1.0, -1.0, -1.0 };
550 right = left;
551
552 double leftHue = 0.0;
553 double rightHue = 0.0;
554
555 bool initialized = false;
556 bool uncut = true;
557
558 for ( int n = 0; n < 12; n++ )
559 {
560 XYZ mid = nthVertex(y, n);
561 if ( mid.x < 0 )
562 continue;
563
564 const double midHue = hueOf( mid );
565 if ( !initialized )
566 {
567 left = mid;
568 right = mid;
569 leftHue = midHue;
570 rightHue = midHue;
571 initialized = true;
572
573 continue;
574 }
575
576 if ( uncut || areInCyclicOrder( leftHue, midHue, rightHue ) )
577 {
578 uncut = false;
579
580 if ( areInCyclicOrder( leftHue, targetHue, midHue ) )
581 {
582 right = mid;
583 rightHue = midHue;
584 }
585 else
586 {
587 left = mid;
588 leftHue = midHue;
589 }
590 }
591 }
592}
593
594static XYZ midpoint( const XYZ& a, const XYZ& b )
595{
596 XYZ r;
597 r.x = ( a.x + b.x ) / 2;
598 r.y = ( a.y + b.y ) / 2;
599 r.z = ( a.z + b.z ) / 2;
600
601 return r;
602}
603
604static int planeBelow( double x )
605{
606 return qFloor( x - 0.5 );
607}
608
609static int planeAbove( double x )
610{
611 return qCeil( x - 0.5 );
612}
613
614static XYZ bisectToLimit( double y, double targetHue )
615{
616 XYZ left, right;
617 bisectToSegment( y, targetHue, left, right );
618
619 double leftHue = hueOf(left);
620
621 for ( int axis = 0; axis < 3; axis++ )
622 {
623 const double l = left.value(axis);
624 const double r = right.value(axis);
625
626 if ( l != r )
627 {
628 int lPlane = -1;
629 int rPlane = 255;
630
631 if ( l < r )
632 {
633 lPlane = planeBelow( trueDelinearized( l ) );
634 rPlane = planeAbove( trueDelinearized( r ) );
635 }
636 else
637 {
638 lPlane = planeAbove( trueDelinearized( l ) );
639 rPlane = planeBelow( trueDelinearized( r ) );
640 }
641
642 for ( int i = 0; i < 8; i++ )
643 {
644 if ( abs( rPlane - lPlane ) <= 1 )
645 break;
646
647 const int mPlane = qFloor( ( lPlane + rPlane ) / 2.0 );
648 const double midPlaneCoordinate = planes[mPlane];
649
650 const XYZ mid = setCoordinate(left, midPlaneCoordinate, right, axis);
651 const double midHue = hueOf( mid );
652
653 if ( areInCyclicOrder( leftHue, targetHue, midHue ) )
654 {
655 right = mid;
656 rPlane = mPlane;
657 }
658 else
659 {
660 left = mid;
661 leftHue = midHue;
662 lPlane = mPlane;
663 }
664 }
665 }
666 }
667
668 return midpoint( left, right );
669}
670
671static double inverseChromaticAdaptation( double adapted )
672{
673 const double adaptedAbs = abs( adapted );
674
675 double base = 27.13 * adaptedAbs / ( 400.0 - adaptedAbs );
676 if ( base < 0.0 )
677 base = 0.0;
678
679 return signum(adapted) * pow( base, 1.0 / 0.42 );
680}
681
682static QRgb findResultByJ( double hueRadians, double chroma, double y )
683{
684 double j = sqrt(y) * 11.0;
685
686 constexpr ViewingConditions vc;
687
688 const double tInnerCoeff = 1.0 / pow( 1.64 - pow( 0.29, vc.backgroundYTowhitePointY ), 0.73 );
689 const double eHue = 0.25 * ( cos( hueRadians + 2.0 ) + 3.8 );
690 const double p1 = eHue * ( 50000.0 / 13.0 ) * vc.nbb;
691 const double hSin = sin(hueRadians);
692 const double hCos = cos(hueRadians);
693
694 for ( int i = 0; i < 5; i++ )
695 {
696 const double jNormalized = j / 100.0;
697 const double alpha = ( chroma == 0.0 || j == 0.0 ) ? 0.0 : chroma / sqrt(jNormalized);
698 const double t = pow( alpha * tInnerCoeff, 1.0 / 0.9 );
699 const double ac = vc.aw * pow( jNormalized, 1.0 / 0.69 / vc.z );
700 const double p2 = ac / vc.nbb;
701
702 const double gamma = 23.0 * ( p2 + 0.305 ) * t /
703 ( 23.0 * p1 + 11 * t * hCos + 108.0 * t * hSin );
704 const double a = gamma * hCos;
705 const double b = gamma * hSin;
706
707 const double rA = ( 460.0 * p2 + 451.0 * a + 288.0 * b ) / 1403.0;
708 const double gA = ( 460.0 * p2 - 891.0 * a - 261.0 * b ) / 1403.0;
709 const double bA = ( 460.0 * p2 - 220.0 * a - 6300.0 * b ) / 1403.0;
710
711 XYZ rgbScaled;
712 rgbScaled.x = inverseChromaticAdaptation( rA );
713 rgbScaled.y = inverseChromaticAdaptation( gA );
714 rgbScaled.z = inverseChromaticAdaptation( bA );
715
716 constexpr XYZ matrix[3] =
717 {
718 { 1373.2198709594231, -1100.4251190754821, -7.278681089101213, },
719 { -271.815969077903, 559.6580465940733, -32.46047482791194 },
720 { 1.9622899599665666, -57.173814538844006, 308.7233197812385 }
721 };
722
723 const XYZ linrgb = matrixMultiply( rgbScaled, matrix );
724
725 if ( linrgb.x < 0 || linrgb.y < 0 || linrgb.z < 0 )
726 return 0;
727
728 const double kR = Y_FROM_LINRGB.x;
729 const double kG = Y_FROM_LINRGB.y;
730 const double kB = Y_FROM_LINRGB.z;
731
732 const double fnj = kR * linrgb.x + kG * linrgb.y + kB * linrgb.z;
733 if ( fnj <= 0 )
734 return 0;
735
736 if ( i == 4 || abs(fnj - y) < 0.002 )
737 {
738 if ( linrgb.x > 100.01 || linrgb.y > 100.01 || linrgb.z > 100.01 )
739 return 0;
740
741 return argbFromLinrgb( linrgb );
742 }
743
744 j = j - ( fnj - y ) * j / ( 2 * fnj );
745 }
746
747 return 0;
748}
749
750static inline QRgb getRgb( double hue, double chroma, double tone )
751{
752 if ( chroma < 0.0001 || tone < 0.0001 || tone > 99.9999 )
753 return argbFromLstar( tone );
754
755 hue = sanitizeDegreesDouble( hue );
756
757 const double hueRadians = hue / 180.0 * M_PI;
758 const double y = yFromLstar( tone );
759
760 const QRgb rgb = findResultByJ( hueRadians, chroma, y );
761 if ( rgb != 0 )
762 return rgb;
763
764 const XYZ linrgb = bisectToLimit( y, hueRadians );
765 return argbFromLinrgb( linrgb );
766}
767
768static const XYZ SRGB_TO_XYZ[3] =
769{
770 { 0.41233895, 0.35762064, 0.18051042 },
771 { 0.2126, 0.7152, 0.0722 },
772 { 0.01932141, 0.11916382, 0.95034478 }
773};
774
775static double linearized( int rgbComponent )
776{
777 const double normalized = rgbComponent / 255.0;
778
779 if ( normalized <= 0.040449936 )
780 return normalized / 12.92 * 100.0;
781 else
782 return pow( ( normalized + 0.055 ) / 1.055, 2.4) * 100.0;
783}
784
785static XYZ xyzFromArgb( QRgb rgb)
786{
787 XYZ xyz;
788
789 xyz.x = linearized( qRed(rgb) );
790 xyz.y = linearized( qGreen(rgb) );
791 xyz.z = linearized( qBlue(rgb) );
792
793 return matrixMultiply( xyz, SRGB_TO_XYZ );
794}
795
796static void getHTC( QRgb rgb, double& hue, double& chroma, double& tone )
797{
798 ViewingConditions vc;
799
800 const XYZ xyz = xyzFromArgb( rgb );
801
802 const double x = xyz.x;
803 const double y = xyz.y;
804 const double z = xyz.z;
805
806 const double rC = 0.401288 * x + 0.650173 * y - 0.051461 * z;
807 const double gC = -0.250268 * x + 1.204414 * y + 0.045854 * z;
808 const double bC = -0.002079 * x + 0.048952 * y + 0.953127 * z;
809
810 const double rD = vc.rgbD.x * rC;
811 const double gD = vc.rgbD.y * gC;
812 const double bD = vc.rgbD.z * bC;
813
814 const double rAF = pow( vc.fl * fabs( rD ) / 100.0, 0.42 );
815 const double gAF = pow( vc.fl * fabs( gD ) / 100.0, 0.42 );
816
817 const double bAF = pow( vc.fl * fabs( bD ) / 100.0, 0.42);
818 const double rA = signum(rD) * 400.0 * rAF / ( rAF + 27.13 );
819 const double gA = signum(gD) * 400.0 * gAF / ( gAF + 27.13 );
820 const double bA = signum(bD) * 400.0 * bAF / ( bAF + 27.13 );
821
822 const double a = ( 11.0 * rA + -12.0 * gA + bA ) / 11.0;
823 const double b = ( rA + gA - 2.0 * bA ) / 9.0;
824 const double u = ( 20.0 * rA + 20.0 * gA + 21.0 * bA ) / 20.0;
825
826 const double p2 = ( 40.0 * rA + 20.0 * gA + bA ) / 20.0;
827
828 // hue
829 const double atanDegrees = atan2(b, a) * 180.0 / M_PI;
830
831 // fmod ???
832 hue = ( atanDegrees < 0 ) ? atanDegrees + 360.0 : atanDegrees >=
833 360 ? atanDegrees - 360 : atanDegrees;
834
835 {
836 const double ac = p2 * vc.nbb;
837
838 const double J = 100.0 * pow( ac / vc.aw, 0.69 * vc.z );
839
840 const double huePrime = ( hue < 20.14 ) ? hue + 360 : hue;
841 const double eHue = ( 1.0 / 4.0 ) * ( cos( huePrime * M_PI / 180.0 + 2.0 ) + 3.8 );
842 const double p1 = 50000.0 / 13.0 * eHue * vc.nbb;
843 const double t = p1 * sqrt(a * a + b * b) / ( u + 0.305 );
844
845 const double alpha =
846 pow(t, 0.9) * pow( 1.64 - pow(0.29, vc.backgroundYTowhitePointY), 0.73);
847
848 chroma = alpha * sqrt(J / 100.0);
849 }
850
851 {
852 tone = 116.0 * labF( y / 100.0 ) - 16.0;
853 }
854}
855
856QskHctColor::QskHctColor( QRgb rgb )
857{
858 getHTC( rgb, m_hue, m_chroma, m_tone );
859}
860
861void QskHctColor::setHue( qreal hue )
862{
863 m_hue = fmod( hue, 360.0 );
864 if ( m_hue < 0.0 )
865 m_hue += 360.0;
866}
867
868void QskHctColor::setChroma( qreal chroma ) noexcept
869{
870 m_chroma = ( chroma < 0.0 ) ? 0.0 : chroma;
871}
872
873void QskHctColor::setTone( qreal tone ) noexcept
874{
875 m_tone = qBound( 0.0, tone, 100.0 );
876}
877
878void QskHctColor::setRgb( QRgb rgb )
879{
880 getHTC( rgb, m_hue, m_chroma, m_tone );
881}
882
883QRgb QskHctColor::rgb() const
884{
885 return getRgb( m_hue, m_chroma, m_tone );
886}
887
888#ifndef QT_NO_DEBUG_STREAM
889
890#include <qdebug.h>
891
892QDebug operator<<( QDebug debug, const QskHctColor& color )
893{
894 debug.nospace() << "HTC("
895 << color.hue() << "," << color.chroma() << "," << color.tone() << ")";
896
897 return debug.space();
898}
899
900#endif