ddnet
Version:
A typescript npm package for interacting with data from ddnet.org
147 lines • 4.96 kB
JavaScript
import { DDNetError } from '../../util.js';
/**
* Helper class to convert or pass around colors.
*
* @typeParam T used internally.
*/
export class Color {
/**
* Stored color in HSLA format.
*/
color = { h: 0, s: 0, l: 0, a: 0 };
/**
* Converts an RGBA color to an HSLA color.
*/
static RGBA_to_HSLA(
/**
* The RGBA color to convert.
*/
rgba) {
let { r, g, b, a } = rgba;
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const delta = max - min;
let h = 0;
if (delta !== 0) {
if (max === r)
h = 60 * (((g - b) / delta) % 6);
else if (max === g)
h = 60 * ((b - r) / delta + 2);
else
h = 60 * ((r - g) / delta + 4);
}
if (h < 0)
h += 360;
let l = (max + min) / 2;
let s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
s *= 100;
l *= 100;
return { h, s, l, a };
}
static from(input) {
//@ts-expect-error
return new Color(input);
}
constructor(input) {
switch (typeof input) {
case 'string': {
let hex = input.slice(1);
if (hex.length === 3 || hex.length === 4) {
hex = hex
.split('')
.map(c => c + c)
.join('');
}
if (hex.length === 6)
hex += 'ff'; // assume full opacity if no alpha
const r = parseInt(hex.slice(0, 2), 16);
const g = parseInt(hex.slice(2, 4), 16);
const b = parseInt(hex.slice(4, 6), 16);
const a = parseInt(hex.slice(6, 8), 16);
this.color = Color.RGBA_to_HSLA({ r, g, b, a });
return;
}
case 'number': {
const byte0 = input & 0xff; // L
const byte1 = (input >> 8) & 0xff; // S
const byte2 = (input >> 16) & 0xff; // H
const byte3 = (input >> 24) & 0xff; // possible alpha
const a = input > 0xffffff || byte3 !== 0 ? byte3 : 255;
let h = byte2, s = byte1, l = byte0;
if (h === 255)
h = 0;
this.color = {
h: (h * 360) / 255,
s: (s * 100) / 255,
l: (l * 100) / 255,
a: a / 255
};
return;
}
default:
break;
}
if ('r' in input) {
this.color = Color.RGBA_to_HSLA(input);
return;
}
if ('h' in input) {
this.color = input;
return;
}
}
to(format) {
switch (format) {
case 'hex': {
const { r, g, b, a } = this.to('rgba');
const toHex = (v) => v.toString(16).padStart(2, '0');
return `#${toHex(r)}${toHex(g)}${toHex(b)}${toHex(a ?? 255)}`;
}
case 'tw': {
const { h, s, l, a } = this.color;
const byteH = Math.round((h / 360) * 255);
const byteS = Math.round((s / 100) * 255);
const byteL = Math.round((l / 100) * 255);
const byteA = a !== undefined && a < 1 ? Math.round(a * 255) : 0;
return (byteA << 24) | (byteH << 16) | (byteS << 8) | byteL;
}
case 'rgba': {
const { h, s, l, a } = this.color;
const c = (n) => Math.round(n);
const lRatio = l / 100;
const sRatio = s / 100;
const cVal = (1 - Math.abs(2 * lRatio - 1)) * sRatio;
const x = cVal * (1 - Math.abs(((h / 60) % 2) - 1));
const m = lRatio - cVal / 2;
let r1 = 0, g1 = 0, b1 = 0;
if (h < 60)
[r1, g1, b1] = [cVal, x, 0];
else if (h < 120)
[r1, g1, b1] = [x, cVal, 0];
else if (h < 180)
[r1, g1, b1] = [0, cVal, x];
else if (h < 240)
[r1, g1, b1] = [0, x, cVal];
else if (h < 300)
[r1, g1, b1] = [x, 0, cVal];
else
[r1, g1, b1] = [cVal, 0, x];
return {
r: c((r1 + m) * 255),
g: c((g1 + m) * 255),
b: c((b1 + m) * 255),
a
};
}
case 'hsla': {
return this.color;
}
default:
throw new DDNetError('Invalid format', format);
}
}
}
//# sourceMappingURL=Color.js.map