|
| 1 | +// color types and functions |
| 2 | +// |
| 3 | +// david lindecrantz <[email protected]> |
| 4 | + |
| 5 | +#pragma once |
| 6 | + |
| 7 | +#include "Common.h" |
| 8 | + |
| 9 | +namespace sfc { |
| 10 | + |
| 11 | +const rgba_t transparent_color = 0x00000000; |
| 12 | + |
| 13 | +// vector<channel_t> to vector<rgba_t> |
| 14 | +inline rgba_vec_t to_rgba(const channel_vec_t& data) { |
| 15 | + if (data.size() % 4 != 0) |
| 16 | + throw std::runtime_error("RGBA vector size not a multiple of 4"); |
| 17 | + rgba_vec_t v(data.size() >> 2); |
| 18 | + for (unsigned i = 0; i < v.size(); ++i) { |
| 19 | + v[i] = (data[i * 4]) + (data[(i * 4) + 1] << 8) + (data[(i * 4) + 2] << 16) + (data[(i * 4) + 3] << 24); |
| 20 | + } |
| 21 | + return v; |
| 22 | +} |
| 23 | + |
| 24 | +// swap bytes between network order and little endian |
| 25 | +constexpr rgba_t reverse_bytes(rgba_t v) { |
| 26 | + return ((v >> 24) & 0xff) | ((v << 8) & 0xff0000) | ((v >> 8) & 0xff00) | ((v << 24) & 0xff000000); |
| 27 | +} |
| 28 | + |
| 29 | +// rgba value to css style hex string |
| 30 | +inline std::string to_hexstring(rgba_t value, bool pound = true, bool alpha = false) { |
| 31 | + return fmt::format("{1}{0:0{2}x}", reverse_bytes(value) >> (alpha ? 0 : 8), pound ? "#" : "", alpha ? 8 : 6); |
| 32 | +} |
| 33 | + |
| 34 | +// css style hex string to rgba value |
| 35 | +inline rgba_t from_hexstring(const std::string& str) { |
| 36 | + std::string s = str; |
| 37 | + if (s.at(0) == '#') |
| 38 | + s.erase(0, 1); |
| 39 | + if (s.size() == 6) |
| 40 | + s.insert(6, 2, 'f'); |
| 41 | + if (s.size() != 8) |
| 42 | + throw std::runtime_error("Argument color-zero not a 6 or 8 character hex-string"); |
| 43 | + uint32_t i; |
| 44 | + if (sscanf(s.c_str(), "%x", &i) == 1) { |
| 45 | + return reverse_bytes(i); |
| 46 | + } else { |
| 47 | + throw std::runtime_error("Failed to interpret argument color-zero"); |
| 48 | + }; |
| 49 | +} |
| 50 | + |
| 51 | +// scale up value using left bit replication |
| 52 | +constexpr channel_t scale_up(channel_t value, unsigned shift) { |
| 53 | + switch (shift) { |
| 54 | + case 7: |
| 55 | + return value ? 0xff : 0; |
| 56 | + case 6: |
| 57 | + return (value << 6) | ((value << 4) & 0x30) | ((value << 2) & 0xc) | (value & 0x3); |
| 58 | + case 5: |
| 59 | + return (value << 5) | ((value << 2) & 0x1c) | ((value >> 1) & 0x3); |
| 60 | + case 4: |
| 61 | + return (value << 4) | (value & 0xf); |
| 62 | + case 3: |
| 63 | + return (value << 3) | ((value >> 2) & 0x7); |
| 64 | + case 2: |
| 65 | + return (value << 2) | ((value >> 4) & 0x3); |
| 66 | + case 1: |
| 67 | + return (value << 1) | ((value >> 6) & 0x1); |
| 68 | + default: |
| 69 | + return (value << shift); |
| 70 | + } |
| 71 | +} |
| 72 | + |
| 73 | +// |
| 74 | +// rgba_color / hsva_color |
| 75 | +// |
| 76 | + |
| 77 | +struct rgba_color final { |
| 78 | + channel_t r = {}; |
| 79 | + channel_t g = {}; |
| 80 | + channel_t b = {}; |
| 81 | + channel_t a = {}; |
| 82 | + |
| 83 | + rgba_color(){}; |
| 84 | + rgba_color(rgba_t c) : r(c & 0x000000ff), g((c & 0x0000ff00) >> 8), b((c & 0x00ff0000) >> 16), a((c & 0xff000000) >> 24){}; |
| 85 | + |
| 86 | + operator rgba_t() { |
| 87 | + rgba_t i = r; |
| 88 | + i += g << 8; |
| 89 | + i += b << 16; |
| 90 | + i += a << 24; |
| 91 | + return i; |
| 92 | + } |
| 93 | + |
| 94 | + bool operator>(const rgba_color& other) const; |
| 95 | +}; |
| 96 | + |
| 97 | +struct hsva_color final { |
| 98 | + float h = {}; |
| 99 | + float s = {}; |
| 100 | + float v = {}; |
| 101 | + float a = {}; |
| 102 | + |
| 103 | + hsva_color(){}; |
| 104 | + hsva_color(const rgba_color& rgba); |
| 105 | + operator rgba_color(); |
| 106 | +}; |
| 107 | + |
| 108 | +// rgba to hsva |
| 109 | +inline hsva_color::hsva_color(const rgba_color& rgba) { |
| 110 | + float rgb_max = (float)fmax(fmax(rgba.r, rgba.g), rgba.b); |
| 111 | + float rgb_min = (float)fmin(fmin(rgba.r, rgba.g), rgba.b); |
| 112 | + float rgb_delta = rgb_max - rgb_min; |
| 113 | + |
| 114 | + if (rgb_delta > 0) { |
| 115 | + if (rgb_max == rgba.r) { |
| 116 | + h = 60.0f * (fmod(((rgba.g - rgba.b) / rgb_delta), 6.0f)); |
| 117 | + } else if (rgb_max == rgba.g) { |
| 118 | + h = 60.0f * (((rgba.b - rgba.r) / rgb_delta) + 2.0f); |
| 119 | + } else if (rgb_max == rgba.b) { |
| 120 | + h = 60.0f * (((rgba.r - rgba.g) / rgb_delta) + 4.0f); |
| 121 | + } |
| 122 | + s = (rgb_max > 0) ? rgb_delta / rgb_max : 0; |
| 123 | + v = rgb_max; |
| 124 | + } else { |
| 125 | + h = 0; |
| 126 | + s = 0; |
| 127 | + v = rgb_max; |
| 128 | + } |
| 129 | + |
| 130 | + if (h < 0) |
| 131 | + h = 360 + h; |
| 132 | + a = rgba.a / 255.0f; |
| 133 | +} |
| 134 | + |
| 135 | +// hsva to rgba |
| 136 | +inline hsva_color::operator rgba_color() { |
| 137 | + rgba_color rgba; |
| 138 | + float c = v * s; |
| 139 | + float p = fmod(h / 60.0f, 6.0f); |
| 140 | + float x = c * (1.0f - fabs(fmod(p, 2.0f) - 1.0f)); |
| 141 | + float m = v - c; |
| 142 | + |
| 143 | + if (0.0f <= p && p < 1.0f) { |
| 144 | + rgba.r = (channel_t)c; |
| 145 | + rgba.g = (channel_t)x; |
| 146 | + rgba.b = 0; |
| 147 | + } else if (1.0f <= p && p < 2.0f) { |
| 148 | + rgba.r = (channel_t)x; |
| 149 | + rgba.g = (channel_t)c; |
| 150 | + rgba.b = 0; |
| 151 | + } else if (2.0f <= p && p < 3.0f) { |
| 152 | + rgba.r = 0; |
| 153 | + rgba.g = (channel_t)c; |
| 154 | + rgba.b = (channel_t)x; |
| 155 | + } else if (3.0f <= p && p < 4.0f) { |
| 156 | + rgba.r = 0; |
| 157 | + rgba.g = (channel_t)x; |
| 158 | + rgba.b = (channel_t)c; |
| 159 | + } else if (4.0f <= p && p < 5.0f) { |
| 160 | + rgba.r = (channel_t)x; |
| 161 | + rgba.g = 0; |
| 162 | + rgba.b = (channel_t)c; |
| 163 | + } else if (5.0f <= p && p < 6.0f) { |
| 164 | + rgba.r = (channel_t)c; |
| 165 | + rgba.g = 0; |
| 166 | + rgba.b = (channel_t)x; |
| 167 | + } else { |
| 168 | + rgba.r = 0; |
| 169 | + rgba.g = 0; |
| 170 | + rgba.b = 0; |
| 171 | + } |
| 172 | + |
| 173 | + rgba.r += (channel_t)m; |
| 174 | + rgba.g += (channel_t)m; |
| 175 | + rgba.b += (channel_t)m; |
| 176 | + rgba.a = (channel_t)(a * 255.0f); |
| 177 | + return rgba; |
| 178 | +} |
| 179 | + |
| 180 | +// aesthetically pleasing color sorting |
| 181 | +inline bool rgba_color::operator>(const rgba_color& o) const { |
| 182 | + const int segments = 8; |
| 183 | + hsva_color hsva = hsva_color(*this); |
| 184 | + hsva_color hsva_o = hsva_color(o); |
| 185 | + |
| 186 | + int h = (int)(segments * hsva.h); |
| 187 | + // int l = (int)(segments * hsva.s); |
| 188 | + int l = (int)(segments * sqrt(0.241f * r + 0.691f * g + 0.068f * b)); |
| 189 | + int v = (int)(segments * hsva.v); |
| 190 | + |
| 191 | + int ho = (int)(segments * hsva_o.h); |
| 192 | + // int lo = (int)(segments * hsva_o.s); |
| 193 | + int lo = (int)(segments * sqrt(0.241f * o.r + 0.691f * o.g + 0.068f * o.b)); |
| 194 | + int vo = (int)(segments * hsva_o.v); |
| 195 | + |
| 196 | + return std::tie(h, l, v) > std::tie(ho, lo, vo); |
| 197 | +} |
| 198 | + |
| 199 | +inline void sort_colors(rgba_vec_t& colors) { |
| 200 | + std::sort(colors.begin(), colors.end(), [](const rgba_t& a, const rgba_t& b) -> bool { return rgba_color(a) > rgba_color(b); }); |
| 201 | +} |
| 202 | + |
| 203 | +} /* namespace sfc */ |
0 commit comments