color-sorter
Version:
Sort colors in a visually pleasing way.
159 lines (158 loc) • 4.17 kB
JavaScript
import { A98RGB, A98RGB_Linear, ColorSpace, HSL, HSV, HWB, LCH, Lab, Lab_D65, OKLCH, OKLab, OKLrab, P3, P3_Linear, ProPhoto, ProPhoto_Linear, REC_2020, REC_2020_Linear, XYZ_ABS_D65, XYZ_D50, XYZ_D65, sRGB, sRGB_Linear, to, tryColor } from "colorjs.io/fn";
//#region index.ts
ColorSpace.register(sRGB);
ColorSpace.register(XYZ_D65);
ColorSpace.register(XYZ_D50);
ColorSpace.register(XYZ_ABS_D65);
ColorSpace.register(Lab_D65);
ColorSpace.register(Lab);
ColorSpace.register(LCH);
ColorSpace.register(sRGB_Linear);
ColorSpace.register(HSL);
ColorSpace.register(HWB);
ColorSpace.register(HSV);
ColorSpace.register(P3_Linear);
ColorSpace.register(P3);
ColorSpace.register(A98RGB_Linear);
ColorSpace.register(A98RGB);
ColorSpace.register(ProPhoto_Linear);
ColorSpace.register(ProPhoto);
ColorSpace.register(REC_2020_Linear);
ColorSpace.register(REC_2020);
ColorSpace.register(OKLab);
ColorSpace.register(OKLCH);
ColorSpace.register(OKLrab);
function numerify(value) {
if (typeof value === "number" && Number.isFinite(value)) return value;
return 0;
}
/**
* Convert a CSS (string) color into a normalized HSL color that can be used for comparison
* @example convert('red')
*/
function convert(authored) {
let parsed = tryColor(authored);
if (parsed === null) return {
hue: 0,
saturation: 0,
lightness: 0,
alpha: 1,
authored
};
let converted = parsed.space.id === "hsl" ? parsed : to(parsed, HSL);
let hsl = converted.coords;
return {
hue: numerify(hsl[0]),
saturation: numerify(hsl[1]),
lightness: numerify(hsl[2]),
alpha: numerify(converted.alpha),
authored
};
}
const RED = 0;
const ORANGE = 1;
const BROWN = 2;
const YELLOW = 3;
const GREEN = 4;
const CYAN = 5;
const BLUE = 6;
const VIOLET = 7;
const MAGENTA = 8;
const PINK = 9;
const GRAY = 10;
const COLOR_GROUP_NAMES = [
"red",
"orange",
"brown",
"yellow",
"green",
"cyan",
"blue",
"violet",
"magenta",
"pink",
"gray"
];
/**
* Get the color's group name, like "red", "green" or "gray"
*
* NB: heavily relies on magic numbers. All done by eye so will probably need tweaking from time to time.
*/
function _color_group(color) {
let { hue, saturation, lightness } = color;
if (saturation < 10) return GRAY;
if (hue < 15 || hue >= 345) return RED;
if (hue < 47) {
if (lightness > 37 && saturation > .5) return ORANGE;
if (saturation < 25 && lightness < 15) return GRAY;
return BROWN;
}
if (hue < 65) {
if (saturation < 25 && lightness < 15) return GRAY;
if (saturation < 70 && lightness < 80) return BROWN;
if (lightness < 30) return GREEN;
return YELLOW;
}
if (hue < 164) return GREEN;
if (hue < 194) {
if (saturation < 20) return GRAY;
return CYAN;
}
if (hue < 241) {
if (saturation < 19) return GRAY;
if (saturation > 55 && lightness > 59 && hue > 234) return VIOLET;
return BLUE;
}
if (hue < 271) return VIOLET;
if (hue < 327) return MAGENTA;
return PINK;
}
/**
* Get the group name of a color
* @example
* ```ts
* const color = convert('rgb(255 0 0)')
* const group = color_group(color) // => 'red'
* ```
*/
function color_group(color) {
return COLOR_GROUP_NAMES[_color_group(color)];
}
const hue_gaps = {
[BLUE]: 48,
[GREEN]: 36,
[RED]: 0,
[GRAY]: 0
};
const default_hue_gap = 24;
const collator = new Intl.Collator("en-US", {
caseFirst: "upper",
sensitivity: "base"
});
function sort_group_fn(a, b, group) {
const hue_gap = hue_gaps[group] ?? default_hue_gap;
if (Math.abs(a.hue - b.hue) < hue_gap) {
const diff = b.lightness - a.lightness;
if (diff !== 0) return diff;
} else if (a.lightness !== b.lightness) return b.lightness - a.lightness;
if (a.alpha !== b.alpha) return b.alpha - a.alpha;
return collator.compare(a.authored, b.authored);
}
/**
* Compare two colors to determine which one is sorted first.
*/
function compare(a, b) {
let group_a = _color_group(a);
let group_b = _color_group(b);
if (group_a === group_b) return sort_group_fn(a, b, group_a);
return group_a - group_b;
}
/**
* Function that sorts colors
* @example ['red', 'yellow'].sort(sort_fn)
*/
function sort_fn(a, b) {
return compare(convert(a), convert(b));
}
//#endregion
export { COLOR_GROUP_NAMES, color_group, compare, convert, sort_fn };