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 | |
18 | using namespace llvm; |
19 | using namespace xray; |
20 | |
21 | // Sequential ColorMaps, which are used to represent information |
22 | // from some minimum to some maximum. |
23 | |
24 | const 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. |
45 | const 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 | |
53 | ColorHelper::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 | |
61 | const 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. |
72 | const 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 | |
77 | ColorHelper::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 |
83 | static std::tuple<double, double, double> |
84 | convertToHSV(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. |
113 | static 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 |
120 | static std::tuple<uint8_t, uint8_t, uint8_t> |
121 | convertToRGB(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 |
152 | static 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 |
171 | static std::tuple<double, double, double> |
172 | interpolateHSV(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 |
181 | std::tuple<uint8_t, uint8_t, uint8_t> |
182 | ColorHelper::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. |
210 | std::string |
211 | ColorHelper::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. |
220 | std::string ColorHelper::getColorString(double Point) const { |
221 | return getColorString(t: getColorTuple(Point)); |
222 | } |
223 | |