Color Palette Generator — Java source
Generate harmonious color palettes - complementary, analogous, triadic, tetradic, and monochromatic - from any base color. Export to CSS, Tailwind, or JSON.
This is the Java implementation — the same logic the interactive tool runs, in a shareable, citable form.
// palette-generator — harmonious palettes from a base color.
// Java 17+, stdlib only. CosmoDev polyglot port of src/lib/colorPalette.ts.
// All inputs clamped; bad hex falls back to black. Display source.
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public final class PaletteGenerator {
/** Parse #rgb / #rrggbb (leading # optional) to bytes; fallback black. */
static int[] hexToRgb(String hex) {
String h = hex == null ? "" : hex.trim().replaceFirst("^#", "");
boolean ok = h.length() == 3 || h.length() == 6;
if (ok) for (char c : h.toCharArray())
if (Character.digit(c, 16) < 0) { ok = false; break; }
if (!ok) return new int[]{0, 0, 0};
if (h.length() == 3) h = "" + h.charAt(0) + h.charAt(0) + h.charAt(1)
+ h.charAt(1) + h.charAt(2) + h.charAt(2); // expand #abc
return new int[]{Integer.parseInt(h.substring(0, 2), 16),
Integer.parseInt(h.substring(2, 4), 16),
Integer.parseInt(h.substring(4, 6), 16)};
}
/** Round half up (TS Math.round), then clamp to a byte. */
static int clampByte(double n) {
return (int) Math.max(0, Math.min(255, Math.floor(n + 0.5)));
}
static String rgbToHex(double r, double g, double b) {
return String.format(Locale.ROOT, "#%02x%02x%02x",
clampByte(r), clampByte(g), clampByte(b));
}
/** hex → HSL. h ∈ [0,360), s/l ∈ [0,100]; achromatic colors give h = 0. */
static double[] hexToHsl(String hex) {
int[] rgb = hexToRgb(hex);
double r = rgb[0] / 255.0, g = rgb[1] / 255.0, b = rgb[2] / 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 new double[]{h * 360, s * 100, l * 100};
}
/** HSL → hex. Java % keeps sign — hence the double-mod true wrap. */
static String hslToHex(double h, double s, double l) {
double H = ((h % 360) + 360) % 360;
double S = Math.max(0, Math.min(100, s)) / 100;
double L = Math.max(0, Math.min(100, l)) / 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 = {{c, x, 0}, {x, c, 0}, {0, c, x}, {0, x, c}, {x, 0, c}, {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. */
static List<String> generatePalette(String baseHex, String scheme, int count) {
double[] t = hexToHsl(baseHex);
double h = t[0], s = t[1], l = t[2];
String base = hslToHex(h, s, l);
switch (scheme) {
case "complement": return List.of(base, hslToHex(h + 180, s, l));
case "split-complement": return List.of(base, hslToHex(h + 150, s, l),
hslToHex(h + 210, s, l));
case "analogous": return List.of(hslToHex(h - 30, s, l), base,
hslToHex(h + 30, s, l));
case "triadic": return List.of(base, hslToHex(h + 120, s, l),
hslToHex(h + 240, s, l));
case "tetradic": return List.of(base, hslToHex(h + 90, s, l),
hslToHex(h + 180, s, l), hslToHex(h + 270, s, l));
case "monochromatic": { // hold h/s, spread lightness in [10, 90]
int n = Math.max(1, count);
double lo = Math.max(10, l - 32), hi = Math.min(90, l + 32);
List<String> out = new ArrayList<>();
for (int i = 0; i < n; i++)
out.add(hslToHex(h, s, n == 1 ? l : lo + (hi - lo) * i / (n - 1)));
return out;
}
default: return List.of(base);
}
}
/** n shades / tints — RGB lerp toward black / white; i runs 1..n so the
* base color itself is never returned, only intermediate steps. */
static List<String> mix(String baseHex, int n, boolean towardWhite) {
int[] rgb = hexToRgb(baseHex);
int steps = Math.max(1, n);
List<String> out = new ArrayList<>();
for (int i = 1; i <= steps; i++) {
double f = (double) i / (steps + 1);
out.add(towardWhite
? rgbToHex(rgb[0] + (255 - rgb[0]) * f, rgb[1] + (255 - rgb[1]) * f,
rgb[2] + (255 - rgb[2]) * f)
: rgbToHex(rgb[0] * (1 - f), rgb[1] * (1 - f), rgb[2] * (1 - f)));
}
return out;
}
static List<String> shades(String baseHex, int n) { return mix(baseHex, n, false); }
static List<String> tints(String baseHex, int n) { return mix(baseHex, n, true); }
private PaletteGenerator() {}
}
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 →