-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathg.cpp
2024 lines (1787 loc) · 73.4 KB
/
g.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
for all versions
sudo apt-get install qt6-base-dev
cd gencolormap
mkdir build
cd build
cmake ..
make
*/
/*
* Copyright (C) 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022
* Computer Graphics Group, University of Siegen
* Written by Martin Lambers <martin.lambers@uni-siegen.de>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/* Notes about the color spaces used internally:
*
* - We use D65 white everywhere
* - RGB means linear RGB; we also have sRGB
* - RGB and sRGB values are in [0,1]
* - XYZ, LUV, and similar values are in the original range (not normalized);
* often this is [0,100]
* - All angles (for hue) are measured in radians
*/
/* Generate color maps for scientific visualization purposes.
*
* Usage:
* - Decide which type of color map you need and how many colors the map should
* contain.
* - Allocate memory for you color map (3 * unsigned char for each color entry).
* - Call the function that generates your color map.
* The return value is always the number of colors that had to be clipped
* to fit into sRGB; you want to keep that number low by adjusting parameters.
*
* All colors are represented as unsigned char sRGB triplets, with each value in
* [0,255].
*/
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <cstring>
#include <cmath>
#include <getopt.h> // https://www.gnu.org/software/libc/manual/html_node/Getopt.html Parsing Long Options with getopt_long
// To communicate extra information back to the program, a few global extern variables are referenced by the program to fetch information from getopt:
extern char *optarg;
extern int optind;
//#include "colormap.hpp"
//#include "export.hpp"
enum type {
brewer_seq,
brewer_div,
brewer_qual,
puseq_lightness,
puseq_saturation,
puseq_rainbow,
puseq_blackbody,
puseq_multihue,
pudiv_lightness,
pudiv_saturation,
puqual_hue,
cubehelix,
moreland,
mcnames
};
enum format {
csv,
json,
ppm
};
/*
* Brewer-like color maps, as described in
* M. Wijffelaars, R. Vliegen, J.J. van Wijk, E.-J. van der Linden. Generating
* color palettes using intuitive parameters. In Computer Graphics Forum, vol. 27,
* no. 3, pp. 743-750, 2008.
*/
// Create a sequential color map with n colors of the given hue in [0,2*PI].
const float BrewerSequentialDefaultHue = 4.18879020479f; // 240 deg
const float BrewerSequentialDefaultContrast = 0.88f;
//float BrewerSequentialDefaultContrastForSmallN(int n); // only for discrete color maps, i.e. n <= 9
const float BrewerSequentialDefaultSaturation = 0.6f;
const float BrewerSequentialDefaultBrightness = 0.75f;
const float BrewerSequentialDefaultWarmth = 0.15f;
// Create a diverging color map with n colors. Half of them will have the given
// hue (in [0,2*PI]), the other half will have a hue that has the distance given
// by divergence (in [0,2*PI]) to that hue, and they will meet in the middle at
// a neutral color.
const float BrewerDivergingDefaultHue = 4.18879020479f; // 240 deg
const float BrewerDivergingDefaultDivergence = 4.18879020479f; // 240 deg = 2/3 * 2PI
const float BrewerDivergingDefaultContrast = 0.88f;
//float BrewerDivergingDefaultContrastForSmallN(int n); // only for discrete color maps, i.e. n <= 9
const float BrewerDivergingDefaultSaturation = 0.6f;
const float BrewerDivergingDefaultBrightness = 0.75f;
const float BrewerDivergingDefaultWarmth = 0.15f;
// Create a qualitative color map with n colors. The colors will have the same
// saturation; lightness and hue will differ. The parameter hue sets the hue of
// the first color, and the parameter divergence defines the hue range starting
// from that hue that can be used for the colors.
const float BrewerQualitativeDefaultHue = 0.0f;
const float BrewerQualitativeDefaultDivergence = 4.18879020479f; // 2/3 * 2PI
const float BrewerQualitativeDefaultContrast = 0.5f;
const float BrewerQualitativeDefaultSaturation = 0.5f;
const float BrewerQualitativeDefaultBrightness = 0.8f;
/*
* Perceptually unifrom (PU) color maps.
*
* These are computed in CIELUV/LCH to achieve approximate perceptual uniformity.
* One or two of lightness, saturation, and hue are changed while the other(s)
* stay constant.
*
* For example, color maps of constant lightness are useful for 3D surfaces
* because they do not interfere with additional shading.
*
* Relevant paper:
* M. Lambers. Interactive Creation of Perceptually Uniform Color Maps.
* Proc. EuroVis Short Papers, May 2020. https://dx.doi.org/10.2312/evs.20201048
*/
/* Sequential perceptually uniform maps */
// Varying lightness
const float PUSequentialLightnessDefaultLightnessRange = 0.95f;
const float PUSequentialLightnessDefaultSaturationRange = 0.95f;
const float PUSequentialLightnessDefaultSaturation = 0.42f;
const float PUSequentialLightnessDefaultHue = 0.349065850399f; // 20 deg
// Varying saturation
const float PUSequentialSaturationDefaultSaturationRange = PUSequentialLightnessDefaultSaturationRange;
const float PUSequentialSaturationDefaultLightness = 0.5f;
const float PUSequentialSaturationDefaultSaturation = PUSequentialLightnessDefaultSaturation;
const float PUSequentialSaturationDefaultHue = 0.349065850399f; // 20 deg
// Varying hue (through all colors, rainbow-like)
const float PUSequentialRainbowDefaultLightnessRange = PUSequentialLightnessDefaultLightnessRange;
const float PUSequentialRainbowDefaultSaturationRange = PUSequentialLightnessDefaultSaturationRange;
const float PUSequentialRainbowDefaultHue = 0.0f;
const float PUSequentialRainbowDefaultRotations = -1.5f;
const float PUSequentialRainbowDefaultSaturation = 0.8f;
// Varying hue (through physically plausible black body colors at increasing
// temperatures).
// The defaults are chosen so that we start at red and arrive at the D65 white
// point (6500 K), thus excluding the blue colors that occur at higher
// temperatures.
const float PUSequentialBlackBodyDefaultTemperature = 250.0f;
const float PUSequentialBlackBodyDefaultTemperatureRange = 6250.0f;
const float PUSequentialBlackBodyDefaultLightnessRange = PUSequentialLightnessDefaultLightnessRange;
const float PUSequentialBlackBodyDefaultSaturationRange = PUSequentialLightnessDefaultSaturationRange;
const float PUSequentialBlackBodyDefaultSaturation = 1.4f;
// Varying hue (user definable)
const float PUSequentialMultiHueDefaultLightnessRange = PUSequentialLightnessDefaultLightnessRange;
const float PUSequentialMultiHueDefaultSaturationRange = PUSequentialSaturationDefaultSaturationRange;
const float PUSequentialMultiHueDefaultSaturation = 0.38f;
const int PUSequentialMultiHueDefaultHues = 2; // number of hues defined in the following lists
const float PUSequentialMultiHueDefaultHueValues[] = { 0.0f, 1.0471975512f }; // hues values in radians in [0,2pi]
const float PUSequentialMultiHueDefaultHuePositions[] = { 0.25f, 0.75f }; // hue positions in [0,1] sorted in ascending order
/* Diverging perceptually uniform maps */
// Varying lightness
const float PUDivergingLightnessDefaultLightnessRange = PUSequentialLightnessDefaultLightnessRange;
const float PUDivergingLightnessDefaultSaturationRange = PUSequentialLightnessDefaultSaturationRange;
const float PUDivergingLightnessDefaultSaturation = PUSequentialLightnessDefaultSaturation;
const float PUDivergingLightnessDefaultHue = 0.349065850399f; // 20 deg
const float PUDivergingLightnessDefaultDivergence = 4.18879020479f; // 2/3 * 2PI
// Varying saturation
const float PUDivergingSaturationDefaultSaturationRange = PUSequentialSaturationDefaultSaturationRange;
const float PUDivergingSaturationDefaultLightness = 0.5f;
const float PUDivergingSaturationDefaultSaturation = 0.45f;
const float PUDivergingSaturationDefaultHue = 0.349065850399f; // 20 deg
const float PUDivergingSaturationDefaultDivergence = 4.18879020479f; // 2/3 * 2PI
/* Qualitative perceptually uniform maps */
const float PUQualitativeHueDefaultHue = 0.0f;
const float PUQualitativeHueDefaultDivergence = 4.18879020479f; // 2/3 * 2PI
const float PUQualitativeHueDefaultLightness = 0.55f;
const float PUQualitativeHueDefaultSaturation = 0.15f;
/*
* CubeHelix color maps, as described in
* Green, D. A., 2011, A colour scheme for the display of astronomical intensity
* images, Bulletin of the Astronomical Society of India, 39, 289.
*/
// Create a CubeHelix colormap with n colors. The parameter hue (in [0,2*PI])
// sets the hue of the first color. The parameter rot sets the number of
// rotations. It can be negative for backwards rotation. The saturation parameter
// determines the saturation of the colors; higher values may lead to clipping
// of colors in the sRGB space. The gamma parameter sets optional gamma correction.
// The return value is the number of colors that had to be clipped.
const float CubeHelixDefaultHue = 0.523598775598f; // 1/12 * 2PI
const float CubeHelixDefaultRotations = -1.5f;
const float CubeHelixDefaultSaturation = 1.2f;
const float CubeHelixDefaultGamma = 1.0f;
/*
* Moreland color maps, as described in
* K. Moreland, Diverging Color Maps for Scientific Visualization, Proc. Int.
* Symp. Visual Computing, December 2009, DOI 10.1007/978-3-642-10520-3_9.
*/
// Create a Moreland colormap with n colors. Specify the two endpoints
// of the colormap as sRGB colors; all intermediate colors will be generated.
const unsigned char MorelandDefaultR0 = 180;
const unsigned char MorelandDefaultG0 = 4;
const unsigned char MorelandDefaultB0 = 38;
const unsigned char MorelandDefaultR1 = 59;
const unsigned char MorelandDefaultG1 = 76;
const unsigned char MorelandDefaultB1 = 192;
/*
* McNames color maps, as described in
* J. McNames, An Effective Color Scale for Simultaneous Color and Gray-Scale Publications,
* IEEE Signal Processing Magazine 23(1), January 2006, DOI 10.1109/MSP.2006.1593340.
*
* Note: Use CubeHelix instead! The McNames color maps are perceptually not linear in luminance!
*/
// Create a McNames colormap with n colors. Specify the number of
// periods.
const float McNamesDefaultPeriods = 2.0f;
/* Generic helpers */
static const float pi = M_PI;
static const float twopi = 2.0 * M_PI;
static float sqr(float x)
{
return x * x;
}
static float clamp(float x, float lo, float hi)
{
return std::min(std::max(x, lo), hi);
}
static float hue_diff(float h0, float h1)
{
float t = std::fabs(h1 - h0);
return (t < pi ? t : twopi - t);
}
static float uchar_to_float(unsigned char x)
{
return x / 255.0f;
}
static unsigned char float_to_uchar(float x, bool* clipped = NULL)
{
if (clipped) {
*clipped = (x < 0.0f || x > 1.0f);
}
int v = std::round(x * 255.0f);
return v < 0 ? 0 : v > 255 ? 255 : v;
}
/* A color triplet class without assumptions about the color space */
class triplet {
public:
struct {
union { float x, l, m, r; };
union { float y, a, u, c, s, g; };
union { float z, b, v, h ; };
};
triplet() {}
triplet(float t0, float t1, float t2) : x(t0), y(t1), z(t2) {}
triplet(const triplet& t) : x(t.x), y(t.y), z(t.z) {}
triplet& operator=(const triplet& t) { x = t.x; y = t.y; z = t.z; return *this; }
};
triplet operator+(triplet t0, triplet t1)
{
return triplet(t0.x + t1.x, t0.y + t1.y, t0.z + t1.z);
}
triplet operator*(float s, triplet t)
{
return triplet(s * t.x, s * t.y, s * t.z);
}
/* XYZ and related color spaces helper functions and values */
static float u_prime(triplet xyz)
{
return 4.0f * xyz.x / (xyz.x + 15.0f * xyz.y + 3.0f * xyz.z);
}
static float v_prime(triplet xyz)
{
return 9.0f * xyz.y / (xyz.x + 15.0f * xyz.y + 3.0f * xyz.z);
}
static const triplet d65_xyz = triplet(95.047f, 100.000f, 108.883f);
static const float d65_u_prime = u_prime(d65_xyz);
static const float d65_v_prime = v_prime(d65_xyz);
static triplet adjust_y(triplet xyz, float new_y)
{
float sum = xyz.x + xyz.y + xyz.z;
// keep old chromaticity in terms of x, y
float x = xyz.x / sum;
float y = xyz.y / sum;
// apply new luminance
float r = new_y / y;
return triplet(r * x, new_y, r * (1.0f - x - y));
}
/* Color space conversion: LCH <-> LUV */
static triplet lch_to_luv(triplet lch)
{
return triplet(lch.l, lch.c * std::cos(lch.h), lch.c * std::sin(lch.h));
}
static triplet luv_to_lch(triplet luv)
{
triplet lch(luv.l, std::hypot(luv.u, luv.v), std::atan2(luv.v, luv.u));
if (lch.h < 0.0f)
lch.h += twopi;
return lch;
}
static float lch_saturation(float l, float c)
{
return c / std::max(l, 1e-8f);
}
static float lch_chroma(float l, float s)
{
return s * l;
}
static float lch_distance(triplet lch0, triplet lch1)
{
/* We have to compute the euclidean distance in LUV space so that it is
* perceptually uniform. But using the equations above we can simplify
* the resulting expression to this: */
return std::sqrt(sqr(lch0.l - lch1.l) + sqr(lch0.c) + sqr(lch1.c)
- 2.0f * lch0.c * lch1.c * std::cos(lch0.h - lch1.h));
}
/* Color space conversion: LUV <-> XYZ */
static triplet luv_to_xyz(triplet luv)
{
if (luv.l <= 0.0f)
return triplet(0.0f, 0.0f, 0.0f);
triplet xyz;
float u_prime = luv.u / (13.0f * luv.l) + d65_u_prime;
float v_prime = luv.v / (13.0f * luv.l) + d65_v_prime;
if (luv.l <= 8.0f) {
xyz.y = d65_xyz.y * luv.l * (3.0f * 3.0f * 3.0f / (29.0f * 29.0f * 29.0f));
} else {
float tmp = (luv.l + 16.0f) / 116.0f;
xyz.y = d65_xyz.y * tmp * tmp * tmp;
}
xyz.x = xyz.y * (9.0f * u_prime) / (4.0f * v_prime);
xyz.z = xyz.y * (12.0f - 3.0f * u_prime - 20.0f * v_prime) / (4.0f * v_prime);
return xyz;
}
static triplet xyz_to_luv(triplet xyz)
{
triplet luv;
float y_ratio = xyz.y / d65_xyz.y;
if (y_ratio <= (6.0f * 6.0f * 6.0f) / (29.0f * 29.0f * 29.0f)) {
luv.l = (29.0f * 29.0f * 29.0f) / (3.0f * 3.0f * 3.0f) * y_ratio;
} else {
luv.l = 116.0f * std::cbrt(y_ratio) - 16.0f;
}
luv.u = 13.0f * luv.l * (u_prime(xyz) - d65_u_prime);
luv.v = 13.0f * luv.l * (v_prime(xyz) - d65_v_prime);
return luv;
}
static float luv_saturation(triplet luv)
{
return lch_saturation(luv.l, std::hypot(luv.u, luv.v));
}
/* Color space conversion: LAB <-> XYZ */
static float lab_invf(float t)
{
if (t > 6.0f / 29.0f)
return t * t * t;
else
return (3.0f * 6.0f * 6.0f) / (29.0f * 29.0f) * (t - 4.0f / 29.0f);
}
static triplet lab_to_xyz(triplet lab)
{
float t = (lab.l + 16.0f) / 116.0f;
return triplet(d65_xyz.x * lab_invf(t + lab.a / 500.0f),
d65_xyz.y * lab_invf(t),
d65_xyz.z * lab_invf(t - lab.b / 200.0f));
}
static float lab_f(float t)
{
if (t > (6.0f * 6.0f * 6.0f) / (29.0f * 29.0f * 29.0f))
return std::cbrt(t);
else
return (29.0f * 29.0f) / (3.0f * 6.0f * 6.0f) * t + 4.0f / 29.0f;
}
static triplet xyz_to_lab(triplet xyz)
{
triplet fxyz = triplet(lab_f(xyz.x / d65_xyz.x), lab_f(xyz.y / d65_xyz.y), lab_f(xyz.z / d65_xyz.z));
return triplet(116.0f * fxyz.y - 16.0f, 500.0f * (fxyz.x - fxyz.y), 200.0f * (fxyz.y - fxyz.z));
}
/* Color space conversion: RGB <-> XYZ
* The matrix entries are the same as used by PBRT3 and Mitsuba2.
* Other values exist, some claiming to be more precise, but we stick
* to these standard values for comparability. */
static triplet rgb_to_xyz(triplet rgb)
{
return 100.0f * triplet(
(0.412453f * rgb.r + 0.357580f * rgb.g + 0.180423f * rgb.b),
(0.212671f * rgb.r + 0.715160f * rgb.g + 0.072169f * rgb.b),
(0.019334f * rgb.r + 0.119193f * rgb.g + 0.950227f * rgb.b));
}
static triplet xyz_to_rgb(triplet xyz)
{
return 0.01f * triplet(
(+3.240479f * xyz.x - 1.537150f * xyz.y - 0.498535f * xyz.z),
(-0.969256f * xyz.x + 1.875991f * xyz.y + 0.041556f * xyz.z),
(+0.055648f * xyz.x - 0.204023f * xyz.y + 1.057311f * xyz.z));
}
/* Color space conversion: RGB <-> sRGB */
static float rgb_to_srgb_helper(float x)
{
return (x <= 0.0031308f ? (x * 12.92f) : (1.055f * std::pow(x, 1.0f / 2.4f) - 0.055f));
}
static triplet rgb_to_srgb(triplet rgb)
{
return triplet(rgb_to_srgb_helper(rgb.r), rgb_to_srgb_helper(rgb.g), rgb_to_srgb_helper(rgb.b));
}
static float srgb_to_rgb_helper(float x)
{
return (x <= 0.04045f ? (x / 12.92f) : std::pow((x + 0.055f) / 1.055f, 2.4f));
}
static triplet srgb_to_rgb(triplet srgb)
{
return triplet(srgb_to_rgb_helper(srgb.r), srgb_to_rgb_helper(srgb.g), srgb_to_rgb_helper(srgb.b));
}
/* Helpers for the conversion to colormap entries */
static bool xyz_to_colormap(triplet xyz, unsigned char* colormap)
{
bool clipped[3];
triplet srgb = rgb_to_srgb(xyz_to_rgb(xyz));
colormap[0] = float_to_uchar(srgb.r, clipped + 0);
colormap[1] = float_to_uchar(srgb.g, clipped + 1);
colormap[2] = float_to_uchar(srgb.b, clipped + 2);
return clipped[0] || clipped[1] || clipped[2];
}
static bool luv_to_colormap(triplet luv, unsigned char* colormap)
{
return xyz_to_colormap(luv_to_xyz(luv), colormap);
}
static bool lch_to_colormap(triplet lch, unsigned char* colormap)
{
return luv_to_colormap(lch_to_luv(lch), colormap);
}
static bool lab_to_colormap(triplet lab, unsigned char* colormap)
{
return xyz_to_colormap(lab_to_xyz(lab), colormap);
}
/* Various helpers */
static float srgb_to_lch_hue(triplet srgb)
{
return luv_to_lch(xyz_to_luv(rgb_to_xyz(srgb_to_rgb(srgb)))).h;
}
// Compute most saturated color that fits into the sRGB
// cube for the given LCH hue value. This is the core
// of the Wijffelaars paper.
static triplet most_saturated_in_srgb(float lch_hue)
{
/* Static values, only computed once */
static float h[] = {
srgb_to_lch_hue(triplet(1, 0, 0)),
srgb_to_lch_hue(triplet(1, 1, 0)),
srgb_to_lch_hue(triplet(0, 1, 0)),
srgb_to_lch_hue(triplet(0, 1, 1)),
srgb_to_lch_hue(triplet(0, 0, 1)),
srgb_to_lch_hue(triplet(1, 0, 1))
};
/* RGB values and variable pointers to them */
int i, j, k;
if (lch_hue < h[0]) {
i = 2;
j = 1;
k = 0;
} else if (lch_hue < h[1]) {
i = 1;
j = 2;
k = 0;
} else if (lch_hue < h[2]) {
i = 0;
j = 2;
k = 1;
} else if (lch_hue < h[3]) {
i = 2;
j = 0;
k = 1;
} else if (lch_hue < h[4]) {
i = 1;
j = 0;
k = 2;
} else if (lch_hue < h[5]) {
i = 0;
j = 1;
k = 2;
} else {
i = 2;
j = 1;
k = 0;
}
/* Compute the missing component */
float srgb[3];
float M[3][3] = {
{ 0.4124f, 0.3576f, 0.1805f },
{ 0.2126f, 0.7152f, 0.0722f },
{ 0.0193f, 0.1192f, 0.9505f }
};
float alpha = -std::sin(lch_hue);
float beta = std::cos(lch_hue);
float T = alpha * d65_u_prime + beta * d65_v_prime;
srgb[j] = 0.0f;
srgb[k] = 1.0f;
float q0 = T * (M[0][k] + 15.0f * M[1][k] + 3.0f * M[2][k]) - (4.0f * alpha * M[0][k] + 9.0f * beta * M[1][k]);
float q1 = T * (M[0][i] + 15.0f * M[1][i] + 3.0f * M[2][i]) - (4.0f * alpha * M[0][i] + 9.0f * beta * M[1][i]);
srgb[i] = rgb_to_srgb_helper(clamp(- q0 / q1, 0.0f, 1.0f));
/* Convert back to LUV */
return xyz_to_luv(rgb_to_xyz(srgb_to_rgb(triplet(srgb[0], srgb[1], srgb[2]))));
}
static float s_max(float l, float h)
{
triplet pmid = most_saturated_in_srgb(h);
triplet pend = triplet(0.0f, 0.0f, 0.0f);
if (l > pmid.l)
pend.l = 100.0f;
float alpha = (pend.l - l) / (pend.l - pmid.l);
float pmids = luv_saturation(pmid);
float pends = luv_saturation(pend);
return alpha * (pmids - pends) + pends;
}
static triplet get_bright_point()
{
static triplet pb(-1.0f, -1.0f, -1.0f);
if (pb.l < 0.0f) {
pb = xyz_to_luv(rgb_to_xyz(triplet(1.0f, 1.0f, 0.0f)));
}
return pb;
}
static float mix_hue(float alpha, float h0, float h1)
{
float M = std::fmod(pi + h1 - h0, twopi) - pi;
return std::fmod(h0 + alpha * M, twopi);
}
static void get_color_points(float hue, float saturation, float warmth,
triplet pb, float pb_hue, float pb_saturation,
triplet* p0, triplet* p1, triplet* p2,
triplet* q0, triplet* q1, triplet* q2)
{
*p0 = lch_to_luv(triplet(0.0f, 0.0f, hue));
*p1 = most_saturated_in_srgb(hue);
triplet p2_lch;
p2_lch.l = (1.0f - warmth) * 100.0f + warmth * pb.l;
p2_lch.h = mix_hue(warmth, hue, pb_hue);
p2_lch.c = lch_chroma(p2_lch.l, std::min(s_max(p2_lch.l, p2_lch.h), warmth * saturation * pb_saturation));
*p2 = lch_to_luv(p2_lch);
*q0 = (1.0f - saturation) * (*p0) + saturation * (*p1);
*q2 = (1.0f - saturation) * (*p2) + saturation * (*p1);
*q1 = 0.5f * ((*q0) + (*q2));
}
static triplet b(triplet b0, triplet b1, triplet b2, float t)
{
float a = (1.0f - t) * (1.0f - t);
float b = 2.0f * (1.0f - t) * t;
float c = t * t;
return a * b0 + b * b1 + c * b2;
}
static float inv_b(float b0, float b1, float b2, float v)
{
return (b0 - b1 + std::sqrt(std::max(b1 * b1 - b0 * b2 + (b0 - 2.0f * b1 + b2) * v, 0.0f)))
/ (b0 - 2.0f * b1 + b2);
}
static triplet get_colormap_entry(float t,
triplet p0, triplet p2,
triplet q0, triplet q1, triplet q2,
float contrast, float brightness)
{
float l = 125 - 125 * std::pow(0.2f, (1.0f - contrast) * brightness + t * contrast);
float T = (l <= q1.l ? 0.5f * inv_b(p0.l, q0.l, q1.l, l) : 0.5f * inv_b(q1.l, q2.l, p2.l, l) + 0.5f);
return (T <= 0.5f ? b(p0, q0, q1, 2.0f * T) : b(q1, q2, p2, 2.0f * (T - 0.5f)));
}
/* Brewer-like color maps */
float BrewerSequentialDefaultContrastForSmallN(int n)
{
return std::min(0.88f, 0.34f + 0.06f * n);
}
int BrewerSequential(int n, unsigned char* colormap, float hue,
float contrast, float saturation, float brightness, float warmth)
{
triplet pb, p0, p1, p2, q0, q1, q2;
pb = get_bright_point();
triplet pb_lch = luv_to_lch(pb);
float pbs = lch_saturation(pb_lch.l, pb_lch.c);
get_color_points(hue, saturation, warmth, pb, pb_lch.h, pbs, &p0, &p1, &p2, &q0, &q1, &q2);
int clipped = 0;
for (int i = 0; i < n; i++) {
float t = (i + 0.5f) / n;
triplet c = get_colormap_entry(t, p0, p2, q0, q1, q2, contrast, brightness);
if (luv_to_colormap(c, colormap + 3 * i))
clipped++;
}
return clipped;
}
float BrewerDivergingDefaultContrastForSmallN(int n)
{
return std::min(0.88f, 0.34f + 0.06f * n);
}
int BrewerDiverging(int n, unsigned char* colormap, float hue, float divergence,
float contrast, float saturation, float brightness, float warmth)
{
float hue1 = hue + divergence;
if (hue1 >= twopi)
hue1 -= twopi;
triplet pb;
triplet p00, p01, p02, q00, q01, q02;
triplet p10, p11, p12, q10, q11, q12;
pb = get_bright_point();
triplet pb_lch = luv_to_lch(pb);
float pbs = lch_saturation(pb_lch.l, pb_lch.c);
get_color_points(hue, saturation, warmth, pb, pb_lch.h, pbs, &p00, &p01, &p02, &q00, &q01, &q02);
get_color_points(hue1, saturation, warmth, pb, pb_lch.h, pbs, &p10, &p11, &p12, &q10, &q11, &q12);
int clipped = 0;
for (int i = 0; i < n; i++) {
triplet c;
if (n % 2 == 1 && i == n / 2) {
// compute neutral color in the middle of the map
triplet c0 = get_colormap_entry(1.0f, p00, p02, q00, q01, q02, contrast, brightness);
triplet c1 = get_colormap_entry(1.0f, p10, p12, q10, q11, q12, contrast, brightness);
if (n <= 9) {
// for discrete color maps, use an extra neutral color
float c0s = luv_saturation(c0);
float c1s = luv_saturation(c1);
float sn = 0.5f * (c0s + c1s) * warmth;
c.l = 0.5f * (c0.l + c1.l);
float cc = lch_chroma(c.l, std::min(s_max(c.l, pb_lch.h), sn));
c = lch_to_luv(triplet(c.l, cc, pb_lch.h));
} else {
// for continuous color maps, use an average, since the extra neutral color looks bad
c = 0.5f * (c0 + c1);
}
} else {
float t = (i + 0.5f) / n;
if (i < n / 2) {
float tt = 2.0f * t;
c = get_colormap_entry(tt, p00, p02, q00, q01, q02, contrast, brightness);
} else {
float tt = 2.0f * (1.0f - t);
c = get_colormap_entry(tt, p10, p12, q10, q11, q12, contrast, brightness);
}
}
if (luv_to_colormap(c, colormap + 3 * i))
clipped++;
}
return clipped;
}
int BrewerQualitative(int n, unsigned char* colormap, float hue, float divergence,
float contrast, float saturation, float brightness)
{
// Get all information about yellow
static triplet ylch(-1.0f, -1.0f, -1.0f);
if (ylch.l < 0.0f) {
ylch = luv_to_lch(xyz_to_luv(rgb_to_xyz(triplet(1.0f, 1.0f, 0.0f))));
}
// Get saturation of red (maximum possible saturation)
static float rs = -1.0f;
if (rs < 0.0f) {
triplet rluv = xyz_to_luv(rgb_to_xyz(triplet(1.0f, 0.0f, 0.0f)));
rs = luv_saturation(rluv);
}
// Derive parameters of the method
float eps = hue / twopi;
float r = divergence / twopi;
float l0 = brightness * ylch.l;
float l1 = (1.0f - contrast) * l0;
// Generate colors
int clipped = 0;
for (int i = 0; i < n; i++) {
float t = (i + 0.5f) / n;
float ch = std::fmod(twopi * (eps + t * r), twopi);
float alpha = hue_diff(ch, ylch.h) / pi;
float cl = (1.0f - alpha) * l0 + alpha * l1;
float cs = std::min(s_max(cl, ch), saturation * rs);
triplet c = lch_to_luv(triplet(cl, lch_chroma(cl, cs), ch));
if (luv_to_colormap(c, colormap + 3 * i))
clipped++;
}
return clipped;
}
/* Perceptually uniform (PU) */
static triplet lch_compute_uniform_lc(float t,
float t0, float t1,
triplet lch0, triplet lch1, float D,
float hue)
{
// t is in [0,1]; s is in [0,1] but relative to [t0,t1]
float s = (t - t0) / (t1 - t0);
triplet lcht;
lcht.h = hue;
lcht.l = (1.0f - s) * lch0.l + s * lch1.l;
// compute four solutions for lcht.c based on two conditions:
float lcht_cs[4];
// 1) the distance between lcht and lch0 is (s * D)
float tmp00 = lch0.c * std::cos(lcht.h - lch0.h);
float tmp01 = std::max(0.0f, sqr(tmp00) - sqr(lcht.l - lch0.l) - sqr(lch0.c) + sqr(s * D));
lcht_cs[0] = tmp00 + std::sqrt(tmp01);
lcht_cs[1] = tmp00 - std::sqrt(tmp01);
// 2) the distance between lcht and lch1 is ((1-s) * D)
float tmp10 = lch1.c * std::cos(lcht.h - lch1.h);
float tmp11 = std::max(0.0f, sqr(tmp10) - sqr(lcht.l - lch1.l) - sqr(lch1.c) + sqr((1.0f - s) * D));
lcht_cs[2] = tmp10 + std::sqrt(tmp11);
lcht_cs[3] = tmp10 - std::sqrt(tmp11);
// find the best solution
float min_c = std::min(lch0.c, lch1.c);
float max_c = std::max(lch0.c, lch1.c);
float min_solution_error = 9999.9f;
int min_solution_error_index = -1;
for (int i = 0; i < 4; i++) {
if (lcht_cs[i] < min_c || lcht_cs[i] > max_c) {
// this solution is invalid
} else {
// solution error is sum of the deviations from condition 1 and 2
float dist_lch0_lcht = lch_distance(lch0, triplet(lcht.l, lcht_cs[i], lcht.h));
float dist_lch1_lcht = lch_distance(lch1, triplet(lcht.l, lcht_cs[i], lcht.h));
float solution_error = std::abs(dist_lch0_lcht - s * D) + std::abs(dist_lch1_lcht - (1.0f - s) * D);
//fprintf(stderr, "t=%g: VALID %i with error %g\n", t, i, solution_error);
if (min_solution_error_index == -1 || solution_error < min_solution_error) {
min_solution_error = solution_error;
min_solution_error_index = i;
}
}
}
if (min_solution_error_index == -1) {
//fprintf(stderr, "TODO: FALLBACK at t=%g\n", t);
lcht.c = 0.5f * (lch0.c + lch1.c);
} else {
lcht.c = lcht_cs[min_solution_error_index];
}
return lcht;
}
int PUSequentialLightness(int n, unsigned char* colormap,
float lightness_range, float saturation_range, float saturation, float hue)
{
triplet lch_00, lch_10, lch_05;
lch_00.l = (1.0f - lightness_range) * 100.0f;
lch_00.c = lch_chroma(lch_00.l, 1.0f - saturation_range);
lch_00.h = hue;
lch_10.l = lightness_range * 100.0f;
lch_10.c = lch_chroma(lch_10.l, 1.0f - saturation_range);
lch_10.h = hue;
lch_05.l = (1.0f - 0.5f) * lch_00.l + 0.5f * lch_10.l;
lch_05.c = lch_chroma(lch_05.l, 5.0f * saturation_range * saturation);
lch_05.h = hue;
// the following distances are actually the same since hue is constant:
float D_00_05 = lch_distance(lch_00, lch_05);
float D_05_10 = lch_distance(lch_05, lch_10);
triplet lch;
int clipped = 0;
for (int i = 0; i < n; i++) {
float t = (i + 0.5f) / n;
if (t <= 0.5f) {
lch = lch_compute_uniform_lc(t, 0.0f, 0.5f, lch_00, lch_05, D_00_05, hue);
} else {
lch = lch_compute_uniform_lc(t, 0.5f, 1.0f, lch_05, lch_10, D_05_10, hue);
}
bool is_clipped = false;
if (lch.c > 100.0f) {
is_clipped = true;
lch.c = 100.0f;
}
if (lch_to_colormap(lch, colormap + 3 * i)) {
is_clipped = true;
}
if (is_clipped)
clipped++;
}
return clipped;
}
int PUSequentialSaturation(int n, unsigned char* colormap,
float saturation_range, float lightness, float saturation, float hue)
{
lightness = std::max(0.01f, lightness * 100.0f);
triplet lch_00, lch_10;
lch_00.l = lightness;
lch_00.c = lch_chroma(lch_00.l, 1.0f - saturation_range);
lch_00.h = hue;
lch_10.l = lightness;
lch_10.c = lch_chroma(lch_10.l, saturation_range * 5.0f * saturation);
lch_10.h = hue;
float D_00_10 = lch_distance(lch_00, lch_10);
triplet lch;
int clipped = 0;
for (int i = 0; i < n; i++) {
float t = (i + 0.5f) / n;
lch = lch_compute_uniform_lc(t, 0.0f, 1.0f, lch_00, lch_10, D_00_10, hue);
bool is_clipped = false;
if (lch.c > 100.0f) {
is_clipped = true;
lch.c = 100.0f;
}
if (lch_to_colormap(lch, colormap + 3 * i)) {
is_clipped = true;
}
if (is_clipped)
clipped++;
}
return clipped;
}
int PUSequentialRainbow(int n, unsigned char* colormap,
float lightness_range, float saturation_range,
float hue, float rotations, float saturation)
{
triplet lch_00, lch_10, lch_05;
lch_00.l = (1.0f - lightness_range) * 100.0f;
lch_00.c = lch_chroma(lch_00.l, 1.0f - saturation_range);
lch_00.h = hue + 0.0f * rotations * twopi;
lch_10.l = lightness_range * 100.0f;
lch_10.c = lch_chroma(lch_10.l, 1.0f - saturation_range);
lch_10.h = hue + 1.0f * rotations * twopi;
lch_05.l = 0.5f * (lch_00.l + lch_10.l);
lch_05.c = lch_chroma(lch_05.l, saturation_range * saturation);
lch_05.h = hue + 0.5f * rotations * twopi;
// the following are not necessarily equal because hue varies:
float D_00_05 = lch_distance(lch_00, lch_05);
float D_05_10 = lch_distance(lch_05, lch_10);
triplet lch;
int clipped = 0;
for (int i = 0; i < n; i++) {
float t = (i + 0.5f) / n;
float h = hue + t * rotations * twopi;
if (t <= 0.5f) {
lch = lch_compute_uniform_lc(t, 0.0f, 0.5f, lch_00, lch_05, D_00_05, h);
} else {
lch = lch_compute_uniform_lc(t, 0.5f, 1.0f, lch_05, lch_10, D_05_10, h);
}
bool is_clipped = false;
if (lch.c > 100.0f) {
is_clipped = true;
lch.c = 100.0f;
}
if (lch_to_colormap(lch, colormap + 3 * i)) {
is_clipped = true;
}
if (is_clipped)
clipped++;
}