77 lines
2.6 KiB
JavaScript
77 lines
2.6 KiB
JavaScript
export function clamp(value, minimum, maximum) {
|
|
return Math.min(maximum, Math.max(minimum, Number(value)));
|
|
}
|
|
|
|
export function normalizeHex(value) {
|
|
const match = /^#?([0-9a-f]{6})$/i.exec(String(value).trim());
|
|
return match ? `#${match[1].toUpperCase()}` : null;
|
|
}
|
|
|
|
export function rgbToHex({ r, g, b }) {
|
|
return `#${[r, g, b]
|
|
.map((channel) => Math.round(clamp(channel, 0, 255)).toString(16).padStart(2, "0"))
|
|
.join("")}`.toUpperCase();
|
|
}
|
|
|
|
export function hexToRgb(value) {
|
|
const hex = normalizeHex(value);
|
|
if (!hex) throw new TypeError("颜色必须是 #RRGGBB");
|
|
return {
|
|
r: Number.parseInt(hex.slice(1, 3), 16),
|
|
g: Number.parseInt(hex.slice(3, 5), 16),
|
|
b: Number.parseInt(hex.slice(5, 7), 16),
|
|
};
|
|
}
|
|
|
|
export function rgbToHsv({ r, g, b }) {
|
|
const red = clamp(r, 0, 255) / 255;
|
|
const green = clamp(g, 0, 255) / 255;
|
|
const blue = clamp(b, 0, 255) / 255;
|
|
const maximum = Math.max(red, green, blue);
|
|
const minimum = Math.min(red, green, blue);
|
|
const delta = maximum - minimum;
|
|
let hue = 0;
|
|
if (delta !== 0) {
|
|
if (maximum === red) hue = 60 * (((green - blue) / delta) % 6);
|
|
else if (maximum === green) hue = 60 * ((blue - red) / delta + 2);
|
|
else hue = 60 * ((red - green) / delta + 4);
|
|
}
|
|
if (hue < 0) hue += 360;
|
|
return {
|
|
h: Math.round(hue),
|
|
s: Math.round(maximum === 0 ? 0 : (delta / maximum) * 100),
|
|
v: Math.round(maximum * 100),
|
|
};
|
|
}
|
|
|
|
export function hsvToRgb({ h, s, v }) {
|
|
const hue = ((Number(h) % 360) + 360) % 360;
|
|
const saturation = clamp(s, 0, 100) / 100;
|
|
const value = clamp(v, 0, 100) / 100;
|
|
const chroma = value * saturation;
|
|
const segment = hue / 60;
|
|
const secondary = chroma * (1 - Math.abs((segment % 2) - 1));
|
|
let channels = [0, 0, 0];
|
|
if (segment < 1) channels = [chroma, secondary, 0];
|
|
else if (segment < 2) channels = [secondary, chroma, 0];
|
|
else if (segment < 3) channels = [0, chroma, secondary];
|
|
else if (segment < 4) channels = [0, secondary, chroma];
|
|
else if (segment < 5) channels = [secondary, 0, chroma];
|
|
else channels = [chroma, 0, secondary];
|
|
const match = value - chroma;
|
|
return {
|
|
r: Math.round((channels[0] + match) * 255),
|
|
g: Math.round((channels[1] + match) * 255),
|
|
b: Math.round((channels[2] + match) * 255),
|
|
};
|
|
}
|
|
|
|
export function updateRecentColors(colors, value, limit = 10) {
|
|
const normalized = normalizeHex(value);
|
|
if (!normalized) return Array.isArray(colors) ? colors.slice(0, limit) : [];
|
|
const existing = Array.isArray(colors)
|
|
? colors.map(normalizeHex).filter(Boolean).filter((color) => color !== normalized)
|
|
: [];
|
|
return [normalized, ...existing].slice(0, limit);
|
|
}
|