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 (C11, stdlib only).
* CosmoDev polyglot port of src/lib/colorPalette.ts. All inputs are clamped;
* nothing throws — unparseable hex falls back to black, like the TS reference.
*/
#include <math.h>
#include <stdio.h>
#include <string.h>
typedef struct { double h, s, l; } Hsl; /* h ∈ [0,360), s/l ∈ [0,100] */
static int hexval(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return -1;
}
/* Parse #rgb / #rrggbb (leading # optional) to bytes; fallback black. */
static void hex_to_rgb(const char *hex, unsigned char out[3]) {
while (*hex == '#' || *hex == ' ') hex++;
char b[7] = {0};
size_t n = strlen(hex);
if (n == 3) { b[0]=b[1]=hex[0]; b[2]=b[3]=hex[1]; b[4]=b[5]=hex[2]; }
else if (n == 6) memcpy(b, hex, 6);
else { out[0]=out[1]=out[2]=0; return; }
for (int i = 0; i < 3; i++) {
int hi = hexval(b[i*2]), lo = hexval(b[i*2+1]);
out[i] = (hi < 0 || lo < 0) ? 0 : (unsigned char)(hi * 16 + lo);
}
}
static int clamp_byte(double n) { /* round half up, then clamp — TS Math.round */
int v = (int)floor(n + 0.5);
return v < 0 ? 0 : v > 255 ? 255 : v;
}
static void rgb_to_hex(double r, double g, double b, char out[8]) {
snprintf(out, 8, "#%02x%02x%02x", clamp_byte(r), clamp_byte(g), clamp_byte(b));
}
/* hex → HSL. Achromatic colors give h = 0, s = 0. */
static Hsl hex_to_hsl(const char *hex) {
unsigned char rgb[3]; hex_to_rgb(hex, rgb);
double r = rgb[0]/255.0, g = rgb[1]/255.0, b = rgb[2]/255.0;
double mx = fmax(r, fmax(g, b)), mn = fmin(r, fmin(g, b)), l = (mx+mn)/2, h = 0, s = 0;
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;
}
Hsl out = { h*360, s*100, l*100 };
return out;
}
/* HSL → hex. Double fmod = true modulo, matching the TS ((h%360)+360)%360. */
static void hsl_to_hex(double h, double s, double l, char out[8]) {
double H = fmod(fmod(h, 360) + 360, 360);
double S = fmin(100, fmax(0, s))/100, L = fmin(100, fmax(0, l))/100;
double c = (1 - fabs(2*L - 1)) * S, x = c * (1 - fabs(fmod(H/60, 2) - 1)), m = L - c/2;
double w[6][3] = {{c,x,0},{x,c,0},{0,c,x},{0,x,c},{x,0,c},{c,0,x}}; /* per 60° sector */
double *p = w[(int)(H / 60)];
rgb_to_hex((p[0]+m)*255, (p[1]+m)*255, (p[2]+m)*255, out);
}
/* Harmony table: hue rotations (deg); count is 2/3/3/3/4. Unknown scheme
* falls through to base-only. `count` steers only monochromatic. */
static const struct { const char *name; int n; double rot[4]; } SCHEMES[] = {
{ "complement", 2, { 0, 180, 0, 0 } },
{ "split-complement", 3, { 0, 150, 210, 0 } },
{ "analogous", 3, { -30, 0, 30, 0 } },
{ "triadic", 3, { 0, 120, 240, 0 } },
{ "tetradic", 4, { 0, 90, 180, 270 } },
};
int generate_palette(const char *base_hex, const char *scheme, int count,
char out[][8], int cap) {
Hsl base = hex_to_hsl(base_hex);
int n = 0;
for (size_t k = 0; k < sizeof SCHEMES / sizeof *SCHEMES; k++) {
if (strcmp(scheme, SCHEMES[k].name) != 0) continue;
for (int i = 0; i < SCHEMES[k].n && n < cap; i++)
hsl_to_hex(base.h + SCHEMES[k].rot[i], base.s, base.l, out[n++]);
return n;
}
if (strcmp(scheme, "monochromatic") == 0) { /* hold h/s, spread l in [10,90] */
int steps = count < 1 ? 1 : count;
double lo = fmax(10, base.l - 32), hi = fmin(90, base.l + 32);
for (int i = 0; i < steps && n < cap; i++) {
double ll = steps == 1 ? base.l : lo + (hi - lo) * i / (steps - 1);
hsl_to_hex(base.h, base.s, ll, out[n++]);
}
return n;
}
hsl_to_hex(base.h, base.s, base.l, out[0]); /* unknown scheme -> base only */
return 1;
}
/* n shades / tints — RGB lerp toward black / white; i runs 1..n so the base
* color itself is never returned, only intermediate steps. */
static void mix(const char *base_hex, int n, char out[][8], int toward_white) {
unsigned char rgb[3]; hex_to_rgb(base_hex, rgb);
int steps = n < 1 ? 1 : n;
for (int i = 1; i <= steps; i++) {
double f = (double)i / (steps + 1);
if (toward_white)
rgb_to_hex(rgb[0]+(255-rgb[0])*f, rgb[1]+(255-rgb[1])*f, rgb[2]+(255-rgb[2])*f, out[i-1]);
else
rgb_to_hex(rgb[0]*(1-f), rgb[1]*(1-f), rgb[2]*(1-f), out[i-1]);
}
}
int shades(const char *base_hex, int n, char out[][8]) { mix(base_hex, n, out, 0); return n < 1 ? 1 : n; }
int tints(const char *base_hex, int n, char out[][8]) { mix(base_hex, n, out, 1); return n < 1 ? 1 : n; }
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 →