I am using this script to find nearest color from a list/array of colors. nearest.js
const colors = ["#FFFFFF", "#520975", "#5D0C8B", "#777779"];
const hexToRgb = hex => hex.slice(1).replace(/^(.)(.)(.)$/gi, "$1$1$2$2$3$3").match(/.{2}/g).map(c => parseInt(c, 16));
const distance = (a, b) => Math.sqrt(Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2) + Math.pow(a[2] - b[2], 2));
const nearestColor = colorHex => colors.reduce((a,v,i,arr) => a = distance(hexToRgb(colorHex), hexToRgb(v)) < a[0] ? [distance(hexToRgb(colorHex), hexToRgb(v)), v] : a, [Number.POSITIVE_INFINITY, colors[0]])[1];
Something is wrong here..
nearestColor("#854B97");
this should return one of this two #520975, #5D0C8B instead it returns a grey color #777779
here is a demo pen https://codepen.io/templatetuners/pen/RwOeLxz
Here is a CIELAB version that seems to work better
/**
* Converts a hexadecimal color value to an RGB array.
* @param {string} hex - The hexadecimal color string (e.g., "#FFFFFF").
* @returns {number[]} An array containing the RGB values [red, green, blue].
*/
const hexToRgb = hex => {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return [r, g, b];
};
/**
* Converts an RGB color array into an XYZ color array using the sRGB color space.
* @param {number[]} rgb - An array of RGB values [red, green, blue].
* @returns {number[]} An array containing the XYZ values.
*/
const rgbToXyz = rgb => {
let [r, g, b] = rgb.map(v => {
v /= 255;
return v > 0.04045 ? Math.pow((v + 0.055) / 1.055, 2.4) : v / 12.92;
});
[r, g, b] = [r * 100, g * 100, b * 100];
const x = r * 0.4124 + g * 0.3576 + b * 0.1805;
const y = r * 0.2126 + g * 0.7152 + b * 0.0722;
const z = r * 0.0193 + g * 0.1192 + b * 0.9505;
return [x, y, z];
};
/**
* Converts an XYZ color array to a CIELAB color array which more closely aligns with human color perception.
* @param {number[]} xyz - An array of XYZ values.
* @returns {number[]} An array containing the CIELAB values [L*, a*, b*].
*/
const xyzToLab = xyz => {
let [x, y, z] = xyz;
[x, y, z] = [x / 95.047, y / 100.000, z / 108.883];
[x, y, z] = [x, y, z].map(v => v > 0.008856 ? Math.pow(v, 1 / 3) : (7.787 * v) + (16 / 116));
const l = (116 * y) - 16;
const a = 500 * (x - y);
const b = 200 * (y - z);
return [l, a, b];
};
/**
* Calculates the Delta E (Euclidean distance in the LAB color space) color difference between two CIELAB colors.
* @param {number[]} labA - The first CIELAB color array.
* @param {number[]} labB - The second CIELAB color array.
* @returns {number} The Delta E color difference.
*/
const deltaE = (labA, labB) => {
return Math.sqrt(
Math.pow(labA[0] - labB[0], 2) +
Math.pow(labA[1] - labB[1], 2) +
Math.pow(labA[2] - labB[2], 2)
);
};
/**
* Finds the nearest color from a predefined set to the given hexadecimal color.
* @param {string} colorHex - The hexadecimal color code.
* @returns {string} The hexadecimal code of the nearest color from the set.
*/
const nearestColor = colorHex => {
const targetLab = xyzToLab(rgbToXyz(hexToRgb(colorHex)));
return colors.reduce((acc, curr) => {
const currLab = xyzToLab(rgbToXyz(hexToRgb(curr)));
const currDeltaE = deltaE(targetLab, currLab);
return currDeltaE < acc.deltaE ? { color: curr, deltaE: currDeltaE } : acc;
}, { color: null, deltaE: Infinity }).color;
};
/** Test the code **/
var nearest = nearestColor('#854B97');
document.getElementById("near").innerHTML = "result<br>" + nearest;
document.getElementById("near").style.backgroundColor = nearest;
<title>Color Distance in CIELAB</title>
<div style="background:#520975; width:100px; height:100px; display:block; float:left; margin:0;">#520975<br>array color</div>
<div style="background:#5D0C8B; width:100px; height:100px; display:block; float:left; margin:0;">#5D0C8B<br>array color</div>
<div style="clear:both;background:#854B97; width:100px; height:100px; display:block; float:left; margin:0;">#854B97<br>find near for this color</div>
<div id="near" style="width:100px; height:100px; display:block; float:left; margin:0;"></div>
<script>
const colors = [
"#B2CFAD", "#F4C6CE", "#7CADD3", "#A4C7E2", "#1BCFC9", "#F7EA5F", "#ffffff", "#520975", "#004987", "#F5E500",
"#FF8300", "#D8262E", "#999899", "#016836", "#005CB8", "#008995", "#D08900", "#EF8F7A", "#F0B2C9", "#EA534D",
"#823550", "#98C11E", "#009ADD", "#FF8188", "#AA7BC9", "#E7E6E5", "#4D9C2D", "#C5093B", "#D7006D", "#970047",
"#10576C", "#00CAC3", "#F1F0E2", "#8FC6E8", "#004876", "#FF637D", "#2E5BBE", "#B0A198", "#FFBE9E", "#7B2F3E",
"#EB6BAF", "#F2F0A1", "#B9AAD4", "#86ad3f", "#AFC9B8", "#D1CCBD", "#D25D12", "#ECCCCE", "#F3CD00", "#006547",
"#789D90", "#80A7BC", "#B8C7D3", "#AF90A7", "#777779", "#5D0C8B", "#01426A", "#F6CE3C", "#00AFA9", "#E11282",
"#F3D09E", "#00A6CE", "#F8B5CC", "#32393C", "#FFDB00", "#EB0028", "#F0B2C9", "#FF6B0B", "#C4D5EC"
];
</script>