From c49b042b62ab84f173b737339a016e2a523c78e8 Mon Sep 17 00:00:00 2001 From: Nyeogmi Date: Mon, 22 Apr 2024 18:16:32 -0700 Subject: [PATCH] Add a basic color palette generator --- slop/palette/.gitignore | 2 + slop/palette/index.mjs | 184 +++++++++++++++++++++++++++++++++ slop/palette/package-lock.json | 30 ++++++ slop/palette/package.json | 16 +++ 4 files changed, 232 insertions(+) create mode 100644 slop/palette/.gitignore create mode 100644 slop/palette/index.mjs create mode 100644 slop/palette/package-lock.json create mode 100644 slop/palette/package.json diff --git a/slop/palette/.gitignore b/slop/palette/.gitignore new file mode 100644 index 0000000..552e899 --- /dev/null +++ b/slop/palette/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +palette.png \ No newline at end of file diff --git a/slop/palette/index.mjs b/slop/palette/index.mjs new file mode 100644 index 0000000..6db7580 --- /dev/null +++ b/slop/palette/index.mjs @@ -0,0 +1,184 @@ +import Color from "colorjs.io/dist/color.js"; +import {PNG} from "pngjs"; +import fs from "fs"; + +function generateColors() { + let pad = function(n) { + while (colors.length < n) { + colors.push(new Color("slategray")); + } + } + + let band = function(colors) { + return function(point, shimmer) { + point += shimmer; + var ix2 = point * colors.length; + while (ix2 < 0) { ix2 += colors.length; } + + return colors[Math.floor(ix2) % colors.length].mix( + colors[Math.floor(ix2 + 1) % colors.length], + ix2 % 1, + {"space": "oklch", "outputSpace": "oklab"} + ) + } + } + + let ramp = function(start, c0, c1, c2) { + // deep base -> center -> pastel + var r01 = c0.range(c1, {space: "oklab", outputSpace: "oklch"}); + var r12 = c1.range(c2, {space: "oklab", outputSpace: "oklch"}); + var n = 5; + var i = 0; + for (var amt = 0; amt < n; amt++) { + colors[start + i * 8] = r01(amt/(n - 1)); + i += 1; + } + n = 3; + for (var amt = 1; amt < n; amt++) { + colors[start + i * 8] = r12(amt/(n - 1)); + i += 1; + } + } + + var colors = []; + + // we were originally appending these + // but for now, just prefill all the slots + pad(64); // so the slots are filled and we can replace them + + // system colors + colors[0] = new Color("#000000"); + colors[1] = new Color("#0000ff"); + colors[2] = new Color("#00ff00"); + colors[3] = new Color("#00ffff"); + colors[4] = new Color("#ff0000"); + colors[5] = new Color("#ff00ff"); + colors[6] = new Color("#ffff00"); + colors[7] = new Color("#ffffff"); + + for (var i = 0; i < 8; i++) { + // colors[i].hsv.h += 135.0; + } + + let lowBand = band([ + new Color("oklch(11.2% 0.0744 268.04)"), + new Color("oklch(15.55% 0.0782 310.39)"), + new Color("oklch(21.51% 0.0864 357.28)"), + new Color("oklch(22.74% 0.0755 43.16)"), + new Color("oklch(23.84% 0.0733 145.59)"), + new Color("oklch(27.4% 0.0444 171.2)"), // NOTE: This reproduces terribly + new Color("oklch(19.1% 0.0396 233.51)"), // NOTE: This reproduces terribly + new Color("oklch(18.63% 0.0575 259.12)"), // NOTE: This reproduces terribly + ]); + + let middleBand = band([ + new Color("oklch(55.34% 0.241 291.34)"), + new Color("oklch(58.54% 0.1994 330.82)"), + new Color("oklch(63.2% 0.2135 20.28)"), + new Color("oklch(80.34% 0.16 70.47)"), + new Color("oklch(91.95% 0.23681720512967358 126.89394948265881)"), + new Color("oklch(87.99% 0.2353 147.34)"), + new Color("oklch(86.44% 0.1505 187.42)"), + new Color("oklch(74.91% 0.1505 235.22)"), + ]); + + let highBand = band([ + new Color("oklch(92.3% 0.041 317.16)"), + new Color("oklch(94.93% 0.0217 358.04)"), + new Color("oklch(95.21% 0.0101 7.31)"), + new Color("oklch(98.41% 0.0172 88.4)"), + new Color("oklch(99.58% 0.00969628196819721 125.6732391579389)"), + new Color("oklch(97.53% 0.0584 127.22)"), + new Color("oklch(96.38% 0.0465 167.45)"), + new Color("oklch(95.21% 0.0475 214.73)"), + ]); + + for (var i = 0; i < 4; i++) { + var lowRampIx = (i - 0.25) / 4; + var low = lowBand(lowRampIx, -0.3); + var middle = middleBand(lowRampIx, 0.0); + colors[8 + i * 2] = low.mix(middle, 0.2/4.0, {space: "oklab"}) + colors[8 + i * 2 + 1] = low.mix(middle, 0.8/4.0, {space: "oklab"}) + colors[8 + i * 2].oklch.c *= 0.4; + colors[8 + i * 2 + 1].oklch.c *= 0.5; + } + for (var i = 0; i < 4; i++) { + for (var c = 1; c < 3; c++) { + var lowRampIx = (i - 0.25) / 4; + var low = lowBand(lowRampIx, -0.3); + var middle = middleBand(lowRampIx, 0.0); + var cbase = low.mix(middle, ([2.1, 3.1, 4.0][c - 1])/4, {space: "oklab"}) + colors[8 + i * 2 + c * 8] = cbase + var color2 = low.mix(middle, ([1.7, 2.7, 3.5][c - 1])/4, {space: "oklab"}) + color2.oklch.c *= 0.6 + colors[8 + i * 2 + 1 + c * 8] = color2; + } + } + for (var i = 0; i < 8; i++) { + colors[32 + i] = middleBand((i - 1) / 8, 0.0); + colors[32 + i + 8] = + middleBand((i - 1) / 8, 0.0).mix(highBand((i - 1) / 8, 0.15), 1.5/5, {space: "oklch"}) + } + for (var i = 0; i < 4; i++) { + var highRampIx = (i - 0.25) / 4; + colors[48 + i * 2] = + middleBand(highRampIx, 0.0).mix(highBand(highRampIx, 0.15), 2.4/5, {space: "oklch"}) + colors[48 + i * 2 + 1] = + middleBand(highRampIx, 0.0).mix(highBand(highRampIx, 0.15), 3.4/5, {space: "oklch"}) + colors[48 + i * 2 + 1].lch.c *= 0.7 + } + for (var i = 0; i < 4; i++) { + var highRampIx = (i - 0.25) / 4; + colors[56 + i * 2] = + middleBand(highRampIx, 0.0).mix(highBand(highRampIx, 0.15), 4.3/5, {space: "oklch"}) + colors[56 + i * 2].lch.c *= 0.6 + colors[56 + i * 2 + 1] = + middleBand(highRampIx, 0.0).mix(highBand(highRampIx, 0.1), 5/5, {space: "oklch"}) + colors[56 + i * 2 + 1].lch.c *= 0.4 + colors[56 + i * 2].lch.l = + colors[56 + i * 2].lch.l * 0.25 + + colors[56 + i * 2 + 1].lch.l * 0.75 + } + /* + for (var i = 0; i < 8; i++) { + colors[8 + i] = middleBand(i/8.0); + } + */ + + + return colors; +} + + +function dump(fname, colors, width, height, scalar) { + if (colors.length != width * height) { + throw new Error("wrong number of colors: " + colors.length); + } + let pngdata = []; + + let clamp = function(x) { + if (x < 0.0) { return 0.0; } + if (x > 255.0) { return 255.0; } + return Math.round(x); + } + + for (var y = 0; y < height * scalar; y++) { + for (var x = 0; x < width * scalar; x++) { + let i = Math.floor(y / scalar) * width + Math.floor(x / scalar); + + pngdata.push(clamp(255 * colors[i].srgb.r)); + pngdata.push(clamp(255 * colors[i].srgb.g)); + pngdata.push(clamp(255 * colors[i].srgb.b)); + pngdata.push(255); + } + } + + var png = new PNG({ + width: width * scalar, + height: height * scalar + }); + png.data = pngdata; + png.pack().pipe(fs.createWriteStream(fname)); +} + +dump("palette.png", generateColors(), 8, 8, 64); \ No newline at end of file diff --git a/slop/palette/package-lock.json b/slop/palette/package-lock.json new file mode 100644 index 0000000..99e202e --- /dev/null +++ b/slop/palette/package-lock.json @@ -0,0 +1,30 @@ +{ + "name": "palette", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "palette", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "colorjs.io": "^0.5.0", + "pngjs": "^7.0.0" + } + }, + "node_modules/colorjs.io": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.0.tgz", + "integrity": "sha512-qekjTiBLM3F/sXKks/ih5aWaHIGu+Ftel0yKEvmpbKvmxpNOhojKgha5uiWEUOqEpRjC1Tq3nJRT7WgdBOxIGg==" + }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "engines": { + "node": ">=14.19.0" + } + } + } +} diff --git a/slop/palette/package.json b/slop/palette/package.json new file mode 100644 index 0000000..d79226f --- /dev/null +++ b/slop/palette/package.json @@ -0,0 +1,16 @@ +{ + "name": "palette", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "main": "node index.mjs", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "colorjs.io": "^0.5.0", + "pngjs": "^7.0.0" + } +}