1//===-- xray-graph.cpp: XRay Function Call Graph Renderer -----------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// A class to get a color from a specified gradient.
10//
11//===----------------------------------------------------------------------===//
12
13#include "xray-color-helper.h"
14#include "llvm/Support/FormatVariadic.h"
15#include "llvm/Support/raw_ostream.h"
16#include <cmath>
17
18using namespace llvm;
19using namespace xray;
20
21// Sequential ColorMaps, which are used to represent information
22// from some minimum to some maximum.
23
24const std::tuple<uint8_t, uint8_t, uint8_t> SequentialMaps[][9] = {
25 {// The greys color scheme from http://colorbrewer2.org/
26 std::make_tuple(args: 255, args: 255, args: 255), std::make_tuple(args: 240, args: 240, args: 240),
27 std::make_tuple(args: 217, args: 217, args: 217), std::make_tuple(args: 189, args: 189, args: 189),
28 std::make_tuple(args: 150, args: 150, args: 150), std::make_tuple(args: 115, args: 115, args: 115),
29 std::make_tuple(args: 82, args: 82, args: 82), std::make_tuple(args: 37, args: 37, args: 37),
30 std::make_tuple(args: 0, args: 0, args: 0)},
31 {// The OrRd color scheme from http://colorbrewer2.org/
32 std::make_tuple(args: 255, args: 247, args: 236), std::make_tuple(args: 254, args: 232, args: 200),
33 std::make_tuple(args: 253, args: 212, args: 158), std::make_tuple(args: 253, args: 187, args: 132),
34 std::make_tuple(args: 252, args: 141, args: 89), std::make_tuple(args: 239, args: 101, args: 72),
35 std::make_tuple(args: 215, args: 48, args: 31), std::make_tuple(args: 179, args: 0, args: 0),
36 std::make_tuple(args: 127, args: 0, args: 0)},
37 {// The PuBu color scheme from http://colorbrewer2.org/
38 std::make_tuple(args: 255, args: 247, args: 251), std::make_tuple(args: 236, args: 231, args: 242),
39 std::make_tuple(args: 208, args: 209, args: 230), std::make_tuple(args: 166, args: 189, args: 219),
40 std::make_tuple(args: 116, args: 169, args: 207), std::make_tuple(args: 54, args: 144, args: 192),
41 std::make_tuple(args: 5, args: 112, args: 176), std::make_tuple(args: 4, args: 90, args: 141),
42 std::make_tuple(args: 2, args: 56, args: 88)}};
43
44// Sequential Maps extend the last colors given out of range inputs.
45const std::tuple<uint8_t, uint8_t, uint8_t> SequentialBounds[][2] = {
46 {// The Bounds for the greys color scheme
47 std::make_tuple(args: 255, args: 255, args: 255), std::make_tuple(args: 0, args: 0, args: 0)},
48 {// The Bounds for the OrRd color Scheme
49 std::make_tuple(args: 255, args: 247, args: 236), std::make_tuple(args: 127, args: 0, args: 0)},
50 {// The Bounds for the PuBu color Scheme
51 std::make_tuple(args: 255, args: 247, args: 251), std::make_tuple(args: 2, args: 56, args: 88)}};
52
53ColorHelper::ColorHelper(ColorHelper::SequentialScheme S)
54 : MinIn(0.0), MaxIn(1.0), ColorMap(SequentialMaps[static_cast<int>(S)]),
55 BoundMap(SequentialBounds[static_cast<int>(S)]) {}
56
57// Diverging ColorMaps, which are used to represent information
58// representing differenes, or a range that goes from negative to positive.
59// These take an input in the range [-1,1].
60
61const std::tuple<uint8_t, uint8_t, uint8_t> DivergingCoeffs[][11] = {
62 {// The PiYG color scheme from http://colorbrewer2.org/
63 std::make_tuple(args: 142, args: 1, args: 82), std::make_tuple(args: 197, args: 27, args: 125),
64 std::make_tuple(args: 222, args: 119, args: 174), std::make_tuple(args: 241, args: 182, args: 218),
65 std::make_tuple(args: 253, args: 224, args: 239), std::make_tuple(args: 247, args: 247, args: 247),
66 std::make_tuple(args: 230, args: 245, args: 208), std::make_tuple(args: 184, args: 225, args: 134),
67 std::make_tuple(args: 127, args: 188, args: 65), std::make_tuple(args: 77, args: 146, args: 33),
68 std::make_tuple(args: 39, args: 100, args: 25)}};
69
70// Diverging maps use out of bounds ranges to show missing data. Missing Right
71// Being below min, and missing left being above max.
72const std::tuple<uint8_t, uint8_t, uint8_t> DivergingBounds[][2] = {
73 {// The PiYG color scheme has green and red for missing right and left
74 // respectively.
75 std::make_tuple(args: 255, args: 0, args: 0), std::make_tuple(args: 0, args: 255, args: 0)}};
76
77ColorHelper::ColorHelper(ColorHelper::DivergingScheme S)
78 : MinIn(-1.0), MaxIn(1.0), ColorMap(DivergingCoeffs[static_cast<int>(S)]),
79 BoundMap(DivergingBounds[static_cast<int>(S)]) {}
80
81// Takes a tuple of uint8_ts representing a color in RGB and converts them to
82// HSV represented by a tuple of doubles
83static std::tuple<double, double, double>
84convertToHSV(const std::tuple<uint8_t, uint8_t, uint8_t> &Color) {
85 double Scaled[3] = {std::get<0>(t: Color) / 255.0, std::get<1>(t: Color) / 255.0,
86 std::get<2>(t: Color) / 255.0};
87 int Min = 0;
88 int Max = 0;
89 for (int i = 1; i < 3; ++i) {
90 if (Scaled[i] < Scaled[Min])
91 Min = i;
92 if (Scaled[i] > Scaled[Max])
93 Max = i;
94 }
95
96 double C = Scaled[Max] - Scaled[Min];
97
98 double HPrime =
99 (C == 0) ? 0 : (Scaled[(Max + 1) % 3] - Scaled[(Max + 2) % 3]) / C;
100 HPrime = HPrime + 2.0 * Max;
101
102 double H = (HPrime < 0) ? (HPrime + 6.0) * 60
103 : HPrime * 60; // Scale to between 0 and 360
104 double V = Scaled[Max];
105
106 double S = (V == 0.0) ? 0.0 : C / V;
107
108 return std::make_tuple(args&: H, args&: S, args&: V);
109}
110
111// Takes a double precision number, clips it between 0 and 1 and then converts
112// that to an integer between 0x00 and 0xFF with proxpper rounding.
113static uint8_t unitIntervalTo8BitChar(double B) {
114 double n = std::clamp(val: B, lo: 0.0, hi: 1.0);
115 return static_cast<uint8_t>(255 * n + 0.5);
116}
117
118// Takes a typle of doubles representing a color in HSV and converts them to
119// RGB represented as a tuple of uint8_ts
120static std::tuple<uint8_t, uint8_t, uint8_t>
121convertToRGB(const std::tuple<double, double, double> &Color) {
122 const double &H = std::get<0>(t: Color);
123 const double &S = std::get<1>(t: Color);
124 const double &V = std::get<2>(t: Color);
125
126 double C = V * S;
127
128 double HPrime = H / 60;
129 double X = C * (1 - std::abs(x: std::fmod(x: HPrime, y: 2.0) - 1));
130
131 double RGB1[3];
132 int HPrimeInt = static_cast<int>(HPrime);
133 if (HPrimeInt % 2 == 0) {
134 RGB1[(HPrimeInt / 2) % 3] = C;
135 RGB1[(HPrimeInt / 2 + 1) % 3] = X;
136 RGB1[(HPrimeInt / 2 + 2) % 3] = 0.0;
137 } else {
138 RGB1[(HPrimeInt / 2) % 3] = X;
139 RGB1[(HPrimeInt / 2 + 1) % 3] = C;
140 RGB1[(HPrimeInt / 2 + 2) % 3] = 0.0;
141 }
142
143 double Min = V - C;
144 double RGB2[3] = {RGB1[0] + Min, RGB1[1] + Min, RGB1[2] + Min};
145
146 return std::make_tuple(args: unitIntervalTo8BitChar(B: RGB2[0]),
147 args: unitIntervalTo8BitChar(B: RGB2[1]),
148 args: unitIntervalTo8BitChar(B: RGB2[2]));
149}
150
151// The Hue component of the HSV interpolation Routine
152static double interpolateHue(double H0, double H1, double T) {
153 double D = H1 - H0;
154 if (H0 > H1) {
155 std::swap(a&: H0, b&: H1);
156
157 D = -D;
158 T = 1 - T;
159 }
160
161 if (D <= 180) {
162 return H0 + T * (H1 - H0);
163 } else {
164 H0 = H0 + 360;
165 return std::fmod(x: H0 + T * (H1 - H0) + 720, y: 360);
166 }
167}
168
169// Interpolates between two HSV Colors both represented as a tuple of doubles
170// Returns an HSV Color represented as a tuple of doubles
171static std::tuple<double, double, double>
172interpolateHSV(const std::tuple<double, double, double> &C0,
173 const std::tuple<double, double, double> &C1, double T) {
174 double H = interpolateHue(H0: std::get<0>(t: C0), H1: std::get<0>(t: C1), T);
175 double S = std::get<1>(t: C0) + T * (std::get<1>(t: C1) - std::get<1>(t: C0));
176 double V = std::get<2>(t: C0) + T * (std::get<2>(t: C1) - std::get<2>(t: C0));
177 return std::make_tuple(args&: H, args&: S, args&: V);
178}
179
180// Get the Color as a tuple of uint8_ts
181std::tuple<uint8_t, uint8_t, uint8_t>
182ColorHelper::getColorTuple(double Point) const {
183 assert(!ColorMap.empty() && "ColorMap must not be empty!");
184 assert(!BoundMap.empty() && "BoundMap must not be empty!");
185
186 if (Point < MinIn)
187 return BoundMap[0];
188 if (Point > MaxIn)
189 return BoundMap[1];
190
191 size_t MaxIndex = ColorMap.size() - 1;
192 double IntervalWidth = MaxIn - MinIn;
193 double OffsetP = Point - MinIn;
194 double SectionWidth = IntervalWidth / static_cast<double>(MaxIndex);
195 size_t SectionNo = std::floor(x: OffsetP / SectionWidth);
196 double T = (OffsetP - SectionNo * SectionWidth) / SectionWidth;
197
198 auto &RGBColor0 = ColorMap[SectionNo];
199 auto &RGBColor1 = ColorMap[std::min(a: SectionNo + 1, b: MaxIndex)];
200
201 auto HSVColor0 = convertToHSV(Color: RGBColor0);
202 auto HSVColor1 = convertToHSV(Color: RGBColor1);
203
204 auto InterpolatedHSVColor = interpolateHSV(C0: HSVColor0, C1: HSVColor1, T);
205 return convertToRGB(Color: InterpolatedHSVColor);
206}
207
208// A helper method to convert a color represented as tuple of uint8s to a hex
209// string.
210std::string
211ColorHelper::getColorString(std::tuple<uint8_t, uint8_t, uint8_t> t) {
212 return std::string(llvm::formatv(Fmt: "#{0:X-2}{1:X-2}{2:X-2}", Vals&: std::get<0>(t&: t),
213 Vals&: std::get<1>(t&: t), Vals&: std::get<2>(t&: t)));
214}
215
216// Gets a color in a gradient given a number in the interval [0,1], it does this
217// by evaluating a polynomial which maps [0, 1] -> [0, 1] for each of the R G
218// and B values in the color. It then converts this [0,1] colors to a 24 bit
219// color as a hex string.
220std::string ColorHelper::getColorString(double Point) const {
221 return getColorString(t: getColorTuple(Point));
222}
223