Color Palette Generator — C++ source
Generate harmonious color palettes - complementary, analogous, triadic, tetradic, and monochromatic - from any base color. Export to CSS, Tailwind, or JSON.
This is the C++ implementation — the same logic the interactive tool runs, in a shareable, citable form.
// palette-generator — harmonious palettes from a base color.
// C++17, stdlib only. CosmoDev polyglot port of src/lib/colorPalette.ts.
// All inputs clamped; bad hex falls back to black. Display source.
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <string>
#include <tuple>
#include <vector>
namespace palette {
using Hsl = std::tuple<double, double, double>; // h ∈ [0,360), s/l ∈ [0,100]
inline int hexVal(char c) {
if (c >= '0' && c <= '9') return c - '0';
return std::tolower(c) - 'a' + 10; // caller has already validated a-f
}
// Parse #rgb / #rrggbb (leading # optional) to bytes; fallback black.
inline std::tuple<int, int, int> hexToRgb(const std::string& hex) {
std::string h = hex;
h.erase(0, h.find_first_not_of("# \t"));
const bool ok = !h.empty() &&
h.find_first_not_of("0123456789abcdefABCDEF") == std::string::npos &&
(h.size() == 3 || h.size() == 6);
if (!ok) return {0, 0, 0};
if (h.size() == 3) h = {h[0], h[0], h[1], h[1], h[2], h[2]}; // expand #abc
return {hexVal(h[0]) * 16 + hexVal(h[1]), hexVal(h[2]) * 16 + hexVal(h[3]),
hexVal(h[4]) * 16 + hexVal(h[5])};
}
inline int clampByte(double n) { // round half up (TS Math.round), then clamp
return (int)std::clamp(std::floor(n + 0.5), 0.0, 255.0);
}
inline std::string rgbToHex(double r, double g, double b) {
char buf[8];
std::snprintf(buf, sizeof buf, "#%02x%02x%02x", clampByte(r), clampByte(g), clampByte(b));
return buf;
}
// hex → HSL. Achromatic colors give h = 0, s = 0.
inline Hsl hexToHsl(const std::string& hex) {
auto [r8, g8, b8] = hexToRgb(hex);
const double r = r8 / 255.0, g = g8 / 255.0, b = b8 / 255.0;
const double mx = std::max({r, g, b}), mn = std::min({r, g, b});
double h = 0, s = 0;
const double l = (mx + mn) / 2;
if (mx != mn) {
const double d = mx - mn;
s = l > 0.5 ? d / (2 - mx - mn) : d / (mx + mn);
if (mx == r) h = (g - b) / d + (g < b ? 6 : 0);
else if (mx == g) h = (b - r) / d + 2;
else h = (r - g) / d + 4;
h /= 6;
}
return {h * 360, s * 100, l * 100};
}
// HSL → hex. Double fmod = true modulo, matching ((h%360)+360)%360 in TS.
inline std::string hslToHex(double h, double s, double l) {
const double H = std::fmod(std::fmod(h, 360) + 360, 360);
const double S = std::clamp(s, 0.0, 100.0) / 100, L = std::clamp(l, 0.0, 100.0) / 100;
const double c = (1 - std::abs(2 * L - 1)) * S;
const double x = c * (1 - std::abs(std::fmod(H / 60, 2) - 1)), m = L - c / 2;
const double w[6][3] = {{c,x,0},{x,c,0},{0,c,x},{0,x,c},{x,0,c},{c,0,x}}; // 60° sectors
const double* p = w[(int)(H / 60)];
return rgbToHex((p[0] + m) * 255, (p[1] + m) * 255, (p[2] + m) * 255);
}
// Generate a harmonious palette. Counts: complement 2, split-complement 3,
// analogous 3, triadic 3, tetradic 4; `count` steers only monochromatic.
inline std::vector<std::string> generatePalette(const std::string& baseHex,
const std::string& scheme,
int count = 5) {
const auto [h, s, l] = hexToHsl(baseHex);
const auto rot = [&](double deg) { return hslToHex(h + deg, s, l); };
const std::string base = hslToHex(h, s, l);
if (scheme == "complement") return {base, rot(180)};
if (scheme == "split-complement") return {base, rot(150), rot(210)};
if (scheme == "analogous") return {rot(-30), base, rot(30)};
if (scheme == "triadic") return {base, rot(120), rot(240)};
if (scheme == "tetradic") return {base, rot(90), rot(180), rot(270)};
if (scheme == "monochromatic") { // hold h/s, spread lightness in [10, 90]
const int n = std::max(1, count);
const double lo = std::max(10.0, l - 32), hi = std::min(90.0, l + 32);
std::vector<std::string> out;
for (int i = 0; i < n; i++)
out.push_back(hslToHex(h, s, n == 1 ? l : lo + (hi - lo) * i / (n - 1)));
return out;
}
return {base};
}
// n shades / tints — RGB lerp toward black / white; i runs 1..n so the base
// color itself is never returned, only intermediate steps.
inline std::vector<std::string> mix(const std::string& baseHex, int n, bool towardWhite) {
auto [r, g, b] = hexToRgb(baseHex);
const int steps = std::max(1, n);
std::vector<std::string> out;
for (int i = 1; i <= steps; i++) {
const double f = (double)i / (steps + 1);
if (towardWhite) out.push_back(rgbToHex(r + (255 - r) * f, g + (255 - g) * f,
b + (255 - b) * f));
else out.push_back(rgbToHex(r * (1 - f), g * (1 - f), b * (1 - f)));
}
return out;
}
inline std::vector<std::string> shades(const std::string& baseHex, int n) {
return mix(baseHex, n, false);
}
inline std::vector<std::string> tints(const std::string& baseHex, int n) {
return mix(baseHex, n, true);
}
} // namespace palette
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →