Color Palette Generator — Kotlin source
Generate harmonious color palettes - complementary, analogous, triadic, tetradic, and monochromatic - from any base color. Export to CSS, Tailwind, or JSON.
This is the Kotlin implementation — the same logic the interactive tool runs, in a shareable, citable form.
// palette-generator — harmonious palettes from a base color.
// Kotlin 1.9+ (JVM), stdlib only. CosmoDev polyglot port of
// src/lib/colorPalette.ts. All inputs clamped; bad hex falls back to black.
import kotlin.math.abs
import kotlin.math.floor
import kotlin.math.max
import kotlin.math.min
object PaletteGenerator {
private val HEX = Regex("^[0-9a-fA-F]+$")
/** Parse #rgb / #rrggbb (leading # optional) to bytes; fallback black. */
fun hexToRgb(hex: String?): Triple<Int, Int, Int> {
val h = (hex ?: "").trim().removePrefix("#")
val ok = (h.length == 3 || h.length == 6) && HEX.matches(h)
if (!ok) return Triple(0, 0, 0)
val s = if (h.length == 3) h.flatMap { listOf(it, it) }.joinToString("") else h
return Triple(s.substring(0, 2).toInt(16), s.substring(2, 4).toInt(16),
s.substring(4).toInt(16))
}
/** Round half up (TS Math.round), then clamp to a byte. */
private fun clampByte(n: Double) = floor(n + 0.5).toInt().coerceIn(0, 255)
private fun rgbToHex(r: Double, g: Double, b: Double) =
"#%02x%02x%02x".format(clampByte(r), clampByte(g), clampByte(b))
/** hex → HSL. h ∈ [0,360), s/l ∈ [0,100]; achromatic colors give h = 0. */
fun hexToHsl(hex: String): Triple<Double, Double, Double> {
val (r8, g8, b8) = hexToRgb(hex)
val r = r8 / 255.0; val g = g8 / 255.0; val b = b8 / 255.0
val mx = max(r, max(g, b)); val mn = min(r, min(g, b))
var h = 0.0; var s = 0.0; val l = (mx + mn) / 2
if (mx != mn) {
val d = mx - mn
s = if (l > 0.5) d / (2 - mx - mn) else d / (mx + mn)
h = (when (mx) {
r -> (g - b) / d + (if (g < b) 6.0 else 0.0)
g -> (b - r) / d + 2
else -> (r - g) / d + 4
}) / 6
}
return Triple(h * 360, s * 100, l * 100)
}
/** HSL → hex. Kotlin % keeps sign — hence the double-mod true wrap. */
fun hslToHex(h: Double, s: Double, l: Double): String {
val H = ((h % 360) + 360) % 360
val S = s.coerceIn(0.0, 100.0) / 100; val L = l.coerceIn(0.0, 100.0) / 100
val c = (1 - abs(2 * L - 1)) * S
val x = c * (1 - abs(H / 60 % 2 - 1)); val m = L - c / 2
val w = arrayOf(doubleArrayOf(c, x, 0.0), doubleArrayOf(x, c, 0.0),
doubleArrayOf(0.0, c, x), doubleArrayOf(0.0, x, c),
doubleArrayOf(x, 0.0, c), doubleArrayOf(c, 0.0, x))
val p = w[min(5.0, H / 60).toInt()] // 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. */
fun generatePalette(baseHex: String, scheme: String, count: Int = 5): List<String> {
val (h, s, l) = hexToHsl(baseHex)
fun rot(deg: Double) = hslToHex(h + deg, s, l)
val base = hslToHex(h, s, l)
return when (scheme) {
"complement" -> listOf(base, rot(180))
"split-complement" -> listOf(base, rot(150), rot(210))
"analogous" -> listOf(rot(-30), base, rot(30))
"triadic" -> listOf(base, rot(120), rot(240))
"tetradic" -> listOf(base, rot(90), rot(180), rot(270))
"monochromatic" -> { // hold h/s, spread lightness in [10, 90]
val n = max(1, count)
val lo = max(10.0, l - 32); val hi = min(90.0, l + 32)
List(n) { i -> hslToHex(h, s, if (n == 1) l else lo + (hi - lo) * i / (n - 1)) }
}
else -> listOf(base)
}
}
/** n shades / tints — RGB lerp toward black / white; i runs 1..n so the
* base color itself is never returned, only intermediate steps. */
fun shades(baseHex: String, n: Int) = mix(baseHex, n) { c, f -> c * (1 - f) }
fun tints(baseHex: String, n: Int) = mix(baseHex, n) { c, f -> c + (255 - c) * f }
private inline fun mix(baseHex: String, n: Int, channel: (Int, Double) -> Double) =
List(max(1, n)) { i ->
val (r, g, b) = hexToRgb(baseHex); val f = (i + 1).toDouble() / (max(1, n) + 1)
rgbToHex(channel(r, f), channel(g, f), channel(b, f))
}
}
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 →