231 lines
5.5 KiB
TypeScript
231 lines
5.5 KiB
TypeScript
export function hsvToRgb(
|
|
h: number,
|
|
s: number,
|
|
v: number
|
|
): [number, number, number] {
|
|
const c = v * s;
|
|
const x = c * (1 - Math.abs(((h * 6) % 2) - 1));
|
|
const m = v - c;
|
|
|
|
let r = 0,
|
|
g = 0,
|
|
b = 0;
|
|
|
|
if (h < 1 / 6) {
|
|
r = c;
|
|
g = x;
|
|
b = 0;
|
|
} else if (h < 2 / 6) {
|
|
r = x;
|
|
g = c;
|
|
b = 0;
|
|
} else if (h < 3 / 6) {
|
|
r = 0;
|
|
g = c;
|
|
b = x;
|
|
} else if (h < 4 / 6) {
|
|
r = 0;
|
|
g = x;
|
|
b = c;
|
|
} else if (h < 5 / 6) {
|
|
r = x;
|
|
g = 0;
|
|
b = c;
|
|
} else {
|
|
r = c;
|
|
g = 0;
|
|
b = x;
|
|
}
|
|
|
|
return [
|
|
Math.round((r + m) * 255),
|
|
Math.round((g + m) * 255),
|
|
Math.round((b + m) * 255),
|
|
];
|
|
}
|
|
|
|
export function rainbowColor(value: number): [number, number, number] {
|
|
const phase = (value / 255.0) * 6;
|
|
const segment = Math.floor(phase);
|
|
const remainder = phase - segment;
|
|
const t = remainder;
|
|
const q = 1 - t;
|
|
|
|
switch (segment % 6) {
|
|
case 0:
|
|
return [255, Math.round(t * 255), 0];
|
|
case 1:
|
|
return [Math.round(q * 255), 255, 0];
|
|
case 2:
|
|
return [0, 255, Math.round(t * 255)];
|
|
case 3:
|
|
return [0, Math.round(q * 255), 255];
|
|
case 4:
|
|
return [Math.round(t * 255), 0, 255];
|
|
case 5:
|
|
return [255, 0, Math.round(q * 255)];
|
|
default:
|
|
return [255, 255, 255];
|
|
}
|
|
}
|
|
|
|
export function thermalColor(value: number): [number, number, number] {
|
|
const t = value / 255.0;
|
|
if (t < 0.25) {
|
|
return [0, 0, Math.round(t * 4 * 255)];
|
|
} else if (t < 0.5) {
|
|
return [0, Math.round((t - 0.25) * 4 * 255), 255];
|
|
} else if (t < 0.75) {
|
|
return [
|
|
Math.round((t - 0.5) * 4 * 255),
|
|
255,
|
|
Math.round((0.75 - t) * 4 * 255),
|
|
];
|
|
} else {
|
|
return [255, 255, Math.round((t - 0.75) * 4 * 255)];
|
|
}
|
|
}
|
|
|
|
export function neonColor(value: number): [number, number, number] {
|
|
const t = value / 255.0;
|
|
const intensity = Math.pow(Math.sin(t * Math.PI), 2);
|
|
const glow = Math.pow(intensity, 0.5);
|
|
return [
|
|
Math.round(glow * 255),
|
|
Math.round(intensity * 255),
|
|
Math.round(Math.pow(intensity, 2) * 255),
|
|
];
|
|
}
|
|
|
|
export function sunsetColor(value: number): [number, number, number] {
|
|
const t = value / 255.0;
|
|
if (t < 0.3) {
|
|
return [Math.round(t * 3.33 * 255), 0, Math.round(t * 1.67 * 255)];
|
|
} else if (t < 0.6) {
|
|
const p = (t - 0.3) / 0.3;
|
|
return [255, Math.round(p * 100), Math.round(50 * (1 - p))];
|
|
} else {
|
|
const p = (t - 0.6) / 0.4;
|
|
return [255, Math.round(100 + p * 155), Math.round(p * 100)];
|
|
}
|
|
}
|
|
|
|
export function oceanColor(value: number): [number, number, number] {
|
|
const t = value / 255.0;
|
|
if (t < 0.25) {
|
|
return [0, Math.round(t * 2 * 255), Math.round(100 + t * 4 * 155)];
|
|
} else if (t < 0.5) {
|
|
const p = (t - 0.25) / 0.25;
|
|
return [0, Math.round(128 + p * 127), 255];
|
|
} else if (t < 0.75) {
|
|
const p = (t - 0.5) / 0.25;
|
|
return [Math.round(p * 100), 255, Math.round(255 - p * 100)];
|
|
} else {
|
|
const p = (t - 0.75) / 0.25;
|
|
return [Math.round(100 + p * 155), 255, Math.round(155 + p * 100)];
|
|
}
|
|
}
|
|
|
|
export function forestColor(value: number): [number, number, number] {
|
|
const t = value / 255.0;
|
|
if (t < 0.3) {
|
|
return [Math.round(t * 2 * 255), Math.round(50 + t * 3 * 205), 0];
|
|
} else if (t < 0.6) {
|
|
const p = (t - 0.3) / 0.3;
|
|
return [Math.round(150 - p * 100), 255, Math.round(p * 100)];
|
|
} else {
|
|
const p = (t - 0.6) / 0.4;
|
|
return [Math.round(50 + p * 100), Math.round(255 - p * 100), Math.round(100 + p * 55)];
|
|
}
|
|
}
|
|
|
|
export function copperColor(value: number): [number, number, number] {
|
|
const t = value / 255.0;
|
|
if (t < 0.4) {
|
|
return [Math.round(t * 2.5 * 255), Math.round(t * 1.5 * 255), Math.round(t * 0.5 * 255)];
|
|
} else if (t < 0.7) {
|
|
const p = (t - 0.4) / 0.3;
|
|
return [255, Math.round(153 + p * 102), Math.round(51 + p * 51)];
|
|
} else {
|
|
const p = (t - 0.7) / 0.3;
|
|
return [255, 255, Math.round(102 + p * 153)];
|
|
}
|
|
}
|
|
|
|
export function ditheredColor(value: number): [number, number, number] {
|
|
const levels = 4;
|
|
const step = 255 / (levels - 1);
|
|
const quantized = Math.round(value / step) * step;
|
|
const error = value - quantized;
|
|
const dither = (Math.random() - 0.5) * 32;
|
|
const final = Math.max(0, Math.min(255, quantized + error + dither));
|
|
return [final, final, final];
|
|
}
|
|
|
|
export function paletteColor(value: number): [number, number, number] {
|
|
const palette = [
|
|
[0, 0, 0],
|
|
[87, 29, 149],
|
|
[191, 82, 177],
|
|
[249, 162, 162],
|
|
[255, 241, 165],
|
|
[134, 227, 206],
|
|
[29, 161, 242],
|
|
[255, 255, 255],
|
|
];
|
|
const index = Math.floor((value / 255.0) * (palette.length - 1));
|
|
return palette[index] as [number, number, number];
|
|
}
|
|
|
|
export function calculateColorDirect(
|
|
absValue: number,
|
|
renderMode: string
|
|
): [number, number, number] {
|
|
switch (renderMode) {
|
|
case 'classic':
|
|
return [absValue, (absValue * 2) % 256, (absValue * 3) % 256];
|
|
|
|
case 'grayscale':
|
|
return [absValue, absValue, absValue];
|
|
|
|
case 'red':
|
|
return [absValue, 0, 0];
|
|
|
|
case 'green':
|
|
return [0, absValue, 0];
|
|
|
|
case 'blue':
|
|
return [0, 0, absValue];
|
|
|
|
case 'forest':
|
|
return forestColor(absValue);
|
|
|
|
case 'copper':
|
|
return copperColor(absValue);
|
|
|
|
case 'rainbow':
|
|
return rainbowColor(absValue);
|
|
|
|
case 'thermal':
|
|
return thermalColor(absValue);
|
|
|
|
case 'neon':
|
|
return neonColor(absValue);
|
|
|
|
case 'sunset':
|
|
return sunsetColor(absValue);
|
|
|
|
case 'ocean':
|
|
return oceanColor(absValue);
|
|
|
|
case 'dithered':
|
|
return ditheredColor(absValue);
|
|
|
|
case 'palette':
|
|
return paletteColor(absValue);
|
|
|
|
default:
|
|
return [absValue, absValue, absValue];
|
|
}
|
|
}
|