| 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 | |