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# (.NET 8), stdlib only. CosmoDev polyglot port of src/lib/colorPalette.ts.
// All inputs clamped; bad hex falls back to black. Display source.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace Palette;
public static class PaletteGenerator
{
// Parse #rgb / #rrggbb (leading # optional) to bytes; fallback black.
public static (int R, int G, int B) HexToRgb(string hex) {
var h = (hex ?? "").Trim().TrimStart('#');
var ok = h.Length is 3 or 6 && h.All(Uri.IsHexDigit);
if (!ok) return (0, 0, 0);
if (h.Length == 3) h = $"{h[0]}{h[0]}{h[1]}{h[1]}{h[2]}{h[2]}"; // #abc
return (Convert.ToInt32(h[..2], 16), Convert.ToInt32(h[2..4], 16),
Convert.ToInt32(h[4..], 16));
}
// Round half up (TS Math.round), then clamp to a byte.
static int ClampByte(double n) => Math.Clamp((int)Math.Floor(n + 0.5), 0, 255);
static string RgbToHex(double r, double g, double b) =>
string.Create(CultureInfo.InvariantCulture,
$"#{ClampByte(r):x2}{ClampByte(g):x2}{ClampByte(b):x2}");
// hex → HSL. h ∈ [0,360), s/l ∈ [0,100]; achromatic colors give h = 0.
public static (double H, double S, double L) HexToHsl(string hex) {
var (r8, g8, b8) = HexToRgb(hex);
double r = r8 / 255.0, g = g8 / 255.0, b = b8 / 255.0;
double mx = Math.Max(r, Math.Max(g, b)), mn = Math.Min(r, Math.Min(g, b));
double h = 0, s = 0, l = (mx + mn) / 2;
if (mx != mn) {
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. C# % keeps sign, hence the double-mod true wrap.
public static string HslToHex(double h, double s, double l) {
double H = ((h % 360) + 360) % 360;
double S = Math.Clamp(s, 0, 100) / 100, L = Math.Clamp(l, 0, 100) / 100;
double c = (1 - Math.Abs(2 * L - 1)) * S;
double x = c * (1 - Math.Abs(H / 60 % 2 - 1)), m = L - c / 2;
double[][] w = { new[] { c, x, 0 }, new[] { x, c, 0 }, new[] { 0, c, x },
new[] { 0, x, c }, new[] { x, 0, c }, new[] { c, 0, x } };
double[] p = w[(int)(H / 60)]; // channel weights per 60° sector
return RgbToHex((p[0] + m) * 255, (p[1] + m) * 255, (p[2] + m) * 255);
}
// Generate a harmony. Counts: complement 2, split-complement 3, analogous
// 3, triadic 3, tetradic 4; count steers only monochromatic.
public static List<string> GeneratePalette(string baseHex, string scheme, int count = 5) {
var (h, s, l) = HexToHsl(baseHex);
string Rot(double deg) => HslToHex(h + deg, s, l);
string Base = HslToHex(h, s, l);
return scheme switch {
"complement" => new() { Base, Rot(180) },
"split-complement" => new() { Base, Rot(150), Rot(210) },
"analogous" => new() { Rot(-30), Base, Rot(30) },
"triadic" => new() { Base, Rot(120), Rot(240) },
"tetradic" => new() { Base, Rot(90), Rot(180), Rot(270) },
"monochromatic" => Mono(h, s, l, Math.Max(1, count)),
_ => new() { Base },
};
}
static List<string> Mono(double h, double s, double l, int n) {
double lo = Math.Max(10, l - 32), hi = Math.Min(90, l + 32); // hold h/s
var out = new List<string>();
for (var i = 0; i < n; i++)
out.Add(HslToHex(h, s, n == 1 ? l : lo + (hi - lo) * i / (n - 1)));
return out;
}
// n shades / tints — RGB lerp toward black / white; i runs 1..n so the
// base color itself is never returned, only intermediate steps.
public static List<string> Shades(string baseHex, int n) =>
Mix(baseHex, n, (c, f) => c * (1 - f));
public static List<string> Tints(string baseHex, int n) =>
Mix(baseHex, n, (c, f) => c + (255 - c) * f);
static List<string> Mix(string baseHex, int n, Func<int, double, double> mix) {
var (r, g, b) = HexToRgb(baseHex);
var steps = Math.Max(1, n);
var out = new List<string>();
for (var i = 1; i <= steps; i++) {
var f = (double)i / (steps + 1);
out.Add(RgbToHex(mix(r, f), mix(g, f), mix(b, f)));
}
return out;
}
}
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 →