generate = {
function getPalette(rgbaColor) {
const labColor = rgb2lab(rgbaColor);
const { palette, mainColorIndex } = generateTonalPalette(labColor);
let mainLAB = palette[mainColorIndex],
mainHCL = lab2hcl(palette[mainColorIndex]),
currentHCL = lab2hcl(labColor),
chromaThreshold = 30 > lab2hcl(palette[5]).C,
dL = mainHCL.L - currentHCL.L,
dC = mainHCL.C - currentHCL.C,
dH = mainHCL.H - currentHCL.H,
refL = lightnessTable[mainColorIndex],
refC = chromaTable[mainColorIndex],
A = 100;
return palette.map(function (inputColor, i) {
if (inputColor === mainLAB)
return (A = Math.max(currentHCL.L - 1.7, 0)), rgbaColor;
const colorHCL = lab2hcl(inputColor);
let newL = colorHCL.L - (lightnessTable[i] / refL) * dL;
newL = Math.min(newL, A);
const color = new ColorHCL(
(colorHCL.H - dH + 360) % 360,
Math.max(
0,
chromaThreshold
? colorHCL.C - dC
: colorHCL.C - dC * Math.min(chromaTable[i] / refC, 1.25)
),
clipRange(newL, 0, 100)
);
A = Math.max(color.L - 1.7, 0);
const hue = (color.H * Math.PI) / 180;
const outputColor = new ColorLAB(
color.L,
color.C * Math.cos(hue),
color.C * Math.sin(hue),
color.alpha
);
let diffL = (color.L + 16) / 116;
function getRatio(a) {
let b = 6 / 29,
c = 3 * Math.pow(b, 2);
return a > b ? Math.pow(a, 3) : c * (a - 4 / 29);
}
const scalar = 0.95047 * getRatio(diffL + outputColor.A / 500);
newL = 1 * getRatio(diffL);
diffL = 1.08883 * getRatio(diffL - outputColor.B / 200);
function transform(a) {
return 0.0031308 >= a
? 12.92 * a
: 1.055 * Math.pow(a, 1 / 2.4) - 0.055;
}
return new ColorRGB(
clipRange(
transform(
3.2404542 * scalar + -1.5371385 * newL + -0.4985314 * diffL
),
0,
1
),
clipRange(
transform(-0.969266 * scalar + 1.8760108 * newL + 0.041556 * diffL),
0,
1
),
clipRange(
transform(0.0556434 * scalar + -0.2040259 * newL + 1.0572252 * diffL),
0,
1
),
color.alpha
);
});
}
function generateTonalPalette(color) {
if (!goldenPalettes.length || !goldenPalettes[0].length)
throw Error("Invalid golden palettes");
let palette = goldenPalettes[0];
let mainColorIndex = -1;
for (let c = Infinity, f = 0; f < goldenPalettes.length; f++) {
for (let h = 0; h < goldenPalettes[f].length && 0 < c; h++) {
let colorCurrent = goldenPalettes[f][h],
avgL = (colorCurrent.L + color.L) / 2,
currentLabDist = Math.sqrt(
Math.pow(colorCurrent.A, 2) + Math.pow(colorCurrent.B, 2)
),
targetLabDist = Math.sqrt(
Math.pow(color.A, 2) + Math.pow(color.B, 2)
),
labDist = (currentLabDist + targetLabDist) / 2;
labDist =
0.5 *
(1 -
Math.sqrt(
Math.pow(labDist, 7) / (Math.pow(labDist, 7) + Math.pow(25, 7))
));
let currentRefA = colorCurrent.A * (1 + labDist),
targetRefA = color.A * (1 + labDist),
currentRefDist = Math.sqrt(
Math.pow(currentRefA, 2) + Math.pow(colorCurrent.B, 2)
),
targetRefDist = Math.sqrt(
Math.pow(targetRefA, 2) + Math.pow(color.B, 2)
);
labDist = targetRefDist - currentRefDist;
let refDist = (currentRefDist + targetRefDist) / 2;
function transform(a, b) {
if (1e-4 > Math.abs(a) && 1e-4 > Math.abs(b)) return 0;
a = (180 * Math.atan2(a, b)) / Math.PI;
return 0 <= a ? a : a + 360;
}
currentRefA = transform(colorCurrent.B, currentRefA);
targetRefA = transform(color.B, targetRefA);
currentRefDist =
2 *
Math.sqrt(currentRefDist * targetRefDist) *
Math.sin(
(((1e-4 > Math.abs(currentLabDist) || 1e-4 > Math.abs(targetLabDist)
? 0
: 180 >= Math.abs(targetRefA - currentRefA)
? targetRefA - currentRefA
: targetRefA <= currentRefA
? targetRefA - currentRefA + 360
: targetRefA - currentRefA - 360) /
2) *
Math.PI) /
180
);
currentLabDist =
1e-4 > Math.abs(currentLabDist) || 1e-4 > Math.abs(targetLabDist)
? 0
: 180 >= Math.abs(targetRefA - currentRefA)
? (currentRefA + targetRefA) / 2
: 360 > currentRefA + targetRefA
? (currentRefA + targetRefA + 360) / 2
: (currentRefA + targetRefA - 360) / 2;
targetLabDist = 1 + 0.045 * refDist;
targetRefDist =
1 +
0.015 *
refDist *
(1 -
0.17 * Math.cos(((currentLabDist - 30) * Math.PI) / 180) +
0.24 * Math.cos((2 * currentLabDist * Math.PI) / 180) +
0.32 * Math.cos(((3 * currentLabDist + 6) * Math.PI) / 180) -
0.2 * Math.cos(((4 * currentLabDist - 63) * Math.PI) / 180));
const result = Math.sqrt(
Math.pow(
(color.L - colorCurrent.L) /
(1 +
(0.015 * Math.pow(avgL - 50, 2)) /
Math.sqrt(20 + Math.pow(avgL - 50, 2))),
2
) +
Math.pow(labDist / (1 * targetLabDist), 2) +
Math.pow(currentRefDist / (1 * targetRefDist), 2) +
(labDist / (1 * targetLabDist)) *
Math.sqrt(
Math.pow(refDist, 7) / (Math.pow(refDist, 7) + Math.pow(25, 7))
) *
Math.sin(
(60 *
Math.exp(-Math.pow((currentLabDist - 275) / 25, 2)) *
Math.PI) /
180
) *
-2 *
(currentRefDist / (1 * targetRefDist))
);
result < c &&
((c = result), (palette = goldenPalettes[f]), (mainColorIndex = h));
}
}
return { palette, mainColorIndex };
}
function parseHexColor(hex) {
if (!/^#[a-fA-F0-9]{3,8}$/.test(hex))
throw Error("Invalid hex color string: " + hex);
hex = hex.substr(1);
let arr;
if (3 === hex.length || 4 === hex.length)
arr = /^(.)(.)(.)(.)?$/
.exec(hex)
?.slice(1, 5)
.map(function (e) {
return e ? e + e : "ff";
});
else if (6 === hex.length || 8 === hex.length)
(arr = /^(..)(..)(..)(..)?$/.exec(hex)?.slice(1, 5)),
undefined === arr[3] && (arr[3] = "ff");
else throw Error("Invalid hex color string: " + hex);
const r = parseHexString(arr[0]) / 255;
const g = parseHexString(arr[1]) / 255;
const b = parseHexString(arr[2]) / 255;
const a = parseHexString(arr[3]) / 255;
return new ColorRGB(r, g, b, a);
}
function parseHexString(a) {
if (!/^[a-fA-F0-9]+$/.test(a)) throw Error("Invalid hex string: " + a);
return parseInt(a, 16);
}
function clipRange(a, b, c) {
return Math.min(Math.max(a, b), c);
}
/**
* Generates a palette for any color input.
* Hue, chroma, and lightness are adjusted by an algorithm that creates palettes that are usable and aesthetically pleasing.
* The shades include lighter and darker options to separate surfaces and provide colors that meet accessibility standards.
*
* @param hex Input color in hex #aabbcc
* @returns Pallette shades
*/
function generate(hex) {
const inputColorRgba = parseHexColor(hex);
const palette = getPalette(inputColorRgba);
const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];
return palette.map((color, i) => {
color.setMeta({
shade: shades[i]
});
return color;
});
}
return generate;
}