snippets/js/svged.js

433 lines
11 KiB
JavaScript
Raw Permalink Normal View History

2024-08-30 15:24:30 +03:00
#!/usr/bin/env node
const { writeFileSync, readFileSync } = require("fs");
/**
* Colour class
* Represet the colour object and it's different types (HEX, RGBA, XYZ, LAB)
* This class have the ability to do the following
* 1. Convert HEX to RGBA
* 2. Convert RGB to XYZ
* 3. Convert XYZ to LAB
* 4. Calculate Delta E00 between two LAB colour (Main purpose)
* @author Ahmed Moussa <moussa.ahmed95@gmail.com>
* @version 2.0
* @link https://github.com/hamada147/IsThisColourSimilar
* @license Apache-2.0
*/
class Color {
/**
* Convert HEX to LAB
* @param {[string]} hex hex colour value desired to be converted to LAB
*/
static hex2lab(hex) {
const [r, g, b, a] = Color.hex2rgba(hex);
const [x, y, z] = Color.rgb2xyz(r, g, b, a);
return Color.xyz2lab(x, y, z); // [l, a, b]
}
/**
* Convert RGBA to LAB
* @param {[Number]} r Red value from 0 to 255
* @param {[Number]} g Green value from 0 to 255
* @param {[Number]} b Blue value from 0 to 255
*/
static rgba2lab(r, g, b, a = 1) {
const [x, y, z] = Color.rgb2xyz(r, g, b, a);
return Color.xyz2lab(x, y, z); // [l, a, b]
}
/**
* Convert LAB to RGBA
* @param {[Number]} l
* @param {[Number]} a
* @param {[Number]} b
*/
static lab2rgba(l, a, b) {
const [x, y, z] = Color.lab2xyz(l, a, b);
return Color.xyz2rgba(x, y, z); // [r, g, b, a]
}
/**
* Convert HEX to RGBA
* @param {[string]} hex hex colour value desired to be converted to RGBA
*/
static hex2rgba(hex) {
let c;
if (hex.charAt(0) === "#") {
c = hex.substring(1).split("");
}
if (c.length > 6 || c.length < 3) {
throw new Error(
`HEX colour must be 3 or 6 values. You provided it ${c.length}`
);
}
if (c.length === 3) {
c = [c[0], c[0], c[1], c[1], c[2], c[2]];
}
c = "0x" + c.join("");
let r = (c >> 16) & 255;
let g = (c >> 8) & 255;
let b = c & 255;
let a = 1;
return [r, g, b, a];
}
/**
* Convert RGB to XYZ
* @param {[Number]} r Red value from 0 to 255
* @param {[Number]} g Green value from 0 to 255
* @param {[Number]} b Blue value from 0 to 255
* @param {Number} [a=1] Obacity value from 0 to 1 with a default value of 1 if not sent
*/
static rgb2xyz(r, g, b, a = 1) {
if (r > 255) {
// console.warn("Red value was higher than 255. It has been set to 255.");
r = 255;
} else if (r < 0) {
// console.warn("Red value was smaller than 0. It has been set to 0.");
r = 0;
}
if (g > 255) {
// console.warn("Green value was higher than 255. It has been set to 255.");
g = 255;
} else if (g < 0) {
// console.warn("Green value was smaller than 0. It has been set to 0.");
g = 0;
}
if (b > 255) {
// console.warn("Blue value was higher than 255. It has been set to 255.");
b = 255;
} else if (b < 0) {
// console.warn("Blue value was smaller than 0. It has been set to 0.");
b = 0;
}
if (a > 1) {
// console.warn("Obacity value was higher than 1. It has been set to 1.");
a = 1;
} else if (a < 0) {
// console.warn("Obacity value was smaller than 0. It has been set to 0.");
a = 0;
}
r = r / 255;
g = g / 255;
b = b / 255;
// step 1
if (r > 0.04045) {
r = Math.pow((r + 0.055) / 1.055, 2.4);
} else {
r = r / 12.92;
}
if (g > 0.04045) {
g = Math.pow((g + 0.055) / 1.055, 2.4);
} else {
g = g / 12.92;
}
if (b > 0.04045) {
b = Math.pow((b + 0.055) / 1.055, 2.4);
} else {
b = b / 12.92;
}
// step 2
r = r * 100;
g = g * 100;
b = b * 100;
// step 3
const x = r * 0.4124564 + g * 0.3575761 + b * 0.1804375;
const y = r * 0.2126729 + g * 0.7151522 + b * 0.072175;
const z = r * 0.0193339 + g * 0.119192 + b * 0.9503041;
return [x, y, z];
}
/**
* Convert XYZ to RGBA
* @param {[Number]} x
* @param {[Number]} y
* @param {[Number]} z
*/
static xyz2rgba(x, y, z) {
let varX = x / 100;
let varY = y / 100;
let varZ = z / 100;
let varR = varX * 3.2404542 + varY * -1.5371385 + varZ * -0.4985314;
let varG = varX * -0.969266 + varY * 1.8760108 + varZ * 0.041556;
let varB = varX * 0.0556434 + varY * -0.2040259 + varZ * 1.0572252;
if (varR > 0.0031308) {
varR = 1.055 * Math.pow(varR, 1 / 2.4) - 0.055;
} else {
varR = 12.92 * varR;
}
if (varG > 0.0031308) {
varG = 1.055 * Math.pow(varG, 1 / 2.4) - 0.055;
} else {
varG = 12.92 * varG;
}
if (varB > 0.0031308) {
varB = 1.055 * Math.pow(varB, 1 / 2.4) - 0.055;
} else {
varB = 12.92 * varB;
}
let r = Math.round(varR * 255);
let g = Math.round(varG * 255);
let b = Math.round(varB * 255);
return [r, g, b, 1];
}
/**
* Convert XYZ to LAB
* @param {[Number]} x Value
* @param {[Number]} y Value
* @param {[Number]} z Value
*/
static xyz2lab(x, y, z) {
// using 10o Observer (CIE 1964)
// CIE10_D65 = {94.811f, 100f, 107.304f} => Daylight
const referenceX = 94.811;
const referenceY = 100;
const referenceZ = 107.304;
// step 1
x = x / referenceX;
y = y / referenceY;
z = z / referenceZ;
// step 2
if (x > 0.008856) {
x = Math.pow(x, 1 / 3);
} else {
x = 7.787 * x + 16 / 116;
}
if (y > 0.008856) {
y = Math.pow(y, 1 / 3);
} else {
y = 7.787 * y + 16 / 116;
}
if (z > 0.008856) {
z = Math.pow(z, 1 / 3);
} else {
z = 7.787 * z + 16 / 116;
}
// step 3
const l = 116 * y - 16;
const a = 500 * (x - y);
const b = 200 * (y - z);
return [l, a, b];
}
/**
* Convert LAB to XYZ
* @param {[Number]} l
* @param {[Number]} a
* @param {[Number]} b
*/
static lab2xyz(l, a, b) {
// using 10o Observer (CIE 1964)
// CIE10_D65 = {94.811f, 100f, 107.304f} => Daylight
const referenceX = 94.811;
const referenceY = 100;
const referenceZ = 107.304;
let varY = (l + 16) / 116;
let varX = a / 500 + varY;
let varZ = varY - b / 200;
if (Math.pow(varY, 3) > 0.008856) {
varY = Math.pow(varY, 3);
} else {
varY = (varY - 16 / 116) / 7.787;
}
if (Math.pow(varX, 3) > 0.008856) {
varX = Math.pow(varX, 3);
} else {
varX = (varX - 16 / 116) / 7.787;
}
if (Math.pow(varZ, 3) > 0.008856) {
varZ = Math.pow(varZ, 3);
} else {
varZ = (varZ - 16 / 116) / 7.787;
}
let x = varX * referenceX;
let y = varY * referenceY;
let z = varZ * referenceZ;
return [x, y, z];
}
/**
* The difference between two given colours with respect to the human eye
* @param {[type]} l1 Colour 1
* @param {[type]} a1 Colour 1
* @param {[type]} b1 Colour 1
* @param {[type]} l2 Colour 2
* @param {[type]} a2 Colour 2
* @param {[type]} b2 Colour 2
*/
static deltaE00(l1, a1, b1, l2, a2, b2) {
// Utility functions added to Math Object
Math.rad2deg = function (rad) {
return (360 * rad) / (2 * Math.PI);
};
Math.deg2rad = function (deg) {
return (2 * Math.PI * deg) / 360;
};
// Start Equation
// Equation exist on the following URL http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE2000.html
const avgL = (l1 + l2) / 2;
const c1 = Math.sqrt(Math.pow(a1, 2) + Math.pow(b1, 2));
const c2 = Math.sqrt(Math.pow(a2, 2) + Math.pow(b2, 2));
const avgC = (c1 + c2) / 2;
const g =
(1 -
Math.sqrt(
Math.pow(avgC, 7) / (Math.pow(avgC, 7) + Math.pow(25, 7))
)) /
2;
const a1p = a1 * (1 + g);
const a2p = a2 * (1 + g);
const c1p = Math.sqrt(Math.pow(a1p, 2) + Math.pow(b1, 2));
const c2p = Math.sqrt(Math.pow(a2p, 2) + Math.pow(b2, 2));
const avgCp = (c1p + c2p) / 2;
let h1p = Math.rad2deg(Math.atan2(b1, a1p));
if (h1p < 0) {
h1p = h1p + 360;
}
let h2p = Math.rad2deg(Math.atan2(b2, a2p));
if (h2p < 0) {
h2p = h2p + 360;
}
const avghp =
Math.abs(h1p - h2p) > 180 ? (h1p + h2p + 360) / 2 : (h1p + h2p) / 2;
const t =
1 -
0.17 * Math.cos(Math.deg2rad(avghp - 30)) +
0.24 * Math.cos(Math.deg2rad(2 * avghp)) +
0.32 * Math.cos(Math.deg2rad(3 * avghp + 6)) -
0.2 * Math.cos(Math.deg2rad(4 * avghp - 63));
let deltahp = h2p - h1p;
if (Math.abs(deltahp) > 180) {
if (h2p <= h1p) {
deltahp += 360;
} else {
deltahp -= 360;
}
}
const deltalp = l2 - l1;
const deltacp = c2p - c1p;
deltahp =
2 * Math.sqrt(c1p * c2p) * Math.sin(Math.deg2rad(deltahp) / 2);
const sl =
1 +
(0.015 * Math.pow(avgL - 50, 2)) /
Math.sqrt(20 + Math.pow(avgL - 50, 2));
const sc = 1 + 0.045 * avgCp;
const sh = 1 + 0.015 * avgCp * t;
const deltaro = 30 * Math.exp(-Math.pow((avghp - 275) / 25, 2));
const rc =
2 *
Math.sqrt(
Math.pow(avgCp, 7) / (Math.pow(avgCp, 7) + Math.pow(25, 7))
);
const rt = -rc * Math.sin(2 * Math.deg2rad(deltaro));
const kl = 1;
const kc = 1;
const kh = 1;
const deltaE = Math.sqrt(
Math.pow(deltalp / (kl * sl), 2) +
Math.pow(deltacp / (kc * sc), 2) +
Math.pow(deltahp / (kh * sh), 2) +
rt * (deltacp / (kc * sc)) * (deltahp / (kh * sh))
);
return deltaE;
}
/**
* Get darker colour of the given colour
* @param {[Number]} r Red value from 0 to 255
* @param {[Number]} g Green value from 0 to 255
* @param {[Number]} b Blue value from 0 to 255
*/
static getDarkerColour(r, g, b, a = 1, darkenPercentage = 0.05) {
let [l1, a1, b1] = Color.rgba2lab(r, g, b, a);
l1 -= l1 * darkenPercentage;
if (l1 < 0) {
l1 = 0;
}
return Color.lab2rgba(l1, a1, b1); // [R, G, B, A]
}
/**
* Get brighter colour of the given colour
* @param {[Number]} r Red value from 0 to 255
* @param {[Number]} g Green value from 0 to 255
* @param {[Number]} b Blue value from 0 to 255
*/
static getBrighterColour(r, g, b, a = 1, brighterPercentage = 0.05) {
let [l1, a1, b1] = Color.rgba2lab(r, g, b, a);
l1 += l1 * brighterPercentage;
if (l1 > 100) {
l1 = 100;
}
return Color.lab2rgba(l1, a1, b1); // [R, G, B, A]
}
}
const CATPPUCCIN_MOCHA = [
Color.hex2lab("#f5e0dc"),
Color.hex2lab("#f2cdcd"),
Color.hex2lab("#f5c2e7"),
Color.hex2lab("#cba6f7"),
Color.hex2lab("#f38ba8"),
Color.hex2lab("#eba0ac"),
Color.hex2lab("#fab387"),
Color.hex2lab("#f9e2af"),
Color.hex2lab("#a6e3a1"),
Color.hex2lab("#94e2d5"),
Color.hex2lab("#89dceb"),
Color.hex2lab("#74c7ec"),
Color.hex2lab("#89b4fa"),
Color.hex2lab("#b4befe"),
Color.hex2lab("#cdd6f4"),
Color.hex2lab("#bac2de"),
Color.hex2lab("#a6adc8"),
Color.hex2lab("#9399b2"),
Color.hex2lab("#7f849c"),
Color.hex2lab("#6c7086"),
Color.hex2lab("#585b70"),
Color.hex2lab("#45475a"),
Color.hex2lab("#313244"),
Color.hex2lab("#1e1e2e"),
Color.hex2lab("#181825"),
Color.hex2lab("#11111b"),
];
let file = process.argv[2];
if (!file) process.exit(1);
let palette = [...CATPPUCCIN_MOCHA];
writeFileSync(
file,
readFileSync(file, "utf8").replace(/#[0-9a-f]{6}/g, (match) => {
let color = Color.hex2lab(match);
let newColor = palette.sort(
(a, b) =>
Color.deltaE00(...color, ...a) - Color.deltaE00(...color, ...b)
)[0];
let [r, g, b] = Color.lab2rgba(...newColor);
let hex =
"#" +
r.toString(16).padStart(2, "0") +
g.toString(16).padStart(2, "0") +
b.toString(16).padStart(2, "0");
return hex;
})
);