Skip to content

Instantly share code, notes, and snippets.

@Roy-Ermers
Created November 14, 2022 23:28
Show Gist options
  • Save Roy-Ermers/cbe32fdf881325353ddf6109e5e84f50 to your computer and use it in GitHub Desktop.
Save Roy-Ermers/cbe32fdf881325353ddf6109e5e84f50 to your computer and use it in GitHub Desktop.
A generic color class with built in converters between different color formats
export default class Color {
get R() {
return Math.round(this.r);
}
get G() {
return Math.round(this.g);
}
get B() {
return Math.round(this.b);
}
private constructor(
private r: number,
private g: number,
private b: number
) {}
private static _getLuminance(r: number, g: number, b: number) {
let R = r / 255;
let G = g / 255;
let B = b / 255;
if (R < 0.03928) R = R / 12.92;
else R = Math.pow((R + 0.055) / 1.055, 2.4);
if (G < 0.03928) G = G / 12.92;
else G = Math.pow((G + 0.055) / 1.055, 2.4);
if (B < 0.03928) B = B / 12.92;
else B = Math.pow((B + 0.055) / 1.055, 2.4);
return 0.2126 * R + 0.7152 * G + 0.0722 * B;
}
private _evaluateContrastRatio() {
const whiteLuminance = Color._getLuminance(255, 255, 255);
const colorLuminance = Color._getLuminance(this.r, this.g, this.b);
const contrast =
(Math.max(whiteLuminance, colorLuminance) + 0.05) /
(Math.min(whiteLuminance, colorLuminance) + 0.05);
return contrast;
}
/**
* Returns the correct text color for this color.
*
* Either `light` or `dark`, based on [WCAG AA](https://www.w3.org/WAI/WCAG2AA-Conformance) standards.
*/
get colorPerception(): "light" | "dark" {
return this._evaluateContrastRatio() >= 3 ? "dark" : "light";
}
/**
* Create a color based on hsl values
*/
static fromHSL(hue: number, saturation: number, lightness: number) {
if (saturation === 0)
return new this(lightness * 255, lightness * 255, lightness * 255);
let p2;
lightness /= 100;
saturation /= 100;
if (lightness <= 0.5) {
p2 = lightness * (1 + saturation);
} else {
p2 = lightness + saturation - lightness * saturation;
}
const p1 = 2 * lightness - p2;
function findRGB(q1: number, q2: number, hue: number) {
if (hue > 360) hue -= 360;
if (hue < 0) hue += 360;
if (hue < 60) return q1 + ((q2 - q1) * hue) / 60;
else if (hue < 180) return q2;
else if (hue < 250) return q1 + ((q2 - q1) * (240 - hue)) / 60;
return q1;
}
return new this(
findRGB(p1, p2, hue + 120) * 255,
findRGB(p1, p2, hue) * 255,
findRGB(p1, p2, hue - 120) * 255
);
}
/**
* Creates a color based on rgb values
*/
static fromRGB(red: number, green: number, blue: number) {
return new this(red, green, blue);
}
/**
* Creates a color based on hex values
*/
static fromHex(hex: number | string) {
if (typeof hex === "number") hex = hex.toString(16);
else if (typeof hex === "string") {
hex = hex.slice(1);
}
if (hex.length === 3) {
hex = [...hex].map((x) => x + x).join("");
}
return new this(
parseInt(hex.substring(0, 2), 16),
parseInt(hex.substring(2, 4), 16),
parseInt(hex.substring(4, 8), 16)
);
}
/**
* Converts the current class to its hex representation.
*/
toHex() {
return (
"#" +
[
this.R.toString(16).padStart(2, "0"),
this.G.toString(16).padStart(2, "0"),
this.B.toString(16).padStart(2, "0"),
].join("")
);
}
/**
* Converts the current class to its RGB representation.
*/
toRGB() {
return `rgb(${this.R}, ${this.G}, ${this.B})`;
}
/**
* Converts the current class to its HSL representation.
*/
toHSL() {
const r = this.r / 255;
const g = this.g / 255;
const b = this.b / 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const chroma = max - min;
const lightness = 0.5 * (max + min);
const saturation =
chroma === 0 ? 0 : chroma / (1 - Math.abs(2 * lightness - 1));
let hue = 0;
if (chroma === 0) {
hue = 0;
} else if (max === r) {
const segment = (g - b) / chroma;
const shift = segment < 0 ? 360 / 60 : 0 / 60;
hue = segment + shift;
} else if (max === g) {
const segment = (b - r) / chroma;
hue = segment + 120 / 60;
} else if (max === b) {
const segment = (r - g) / chroma;
hue = segment + 240 / 60;
}
hue *= 60;
if (hue < 0) {
hue += 360;
}
return `hsl(${hue}deg, ${Math.round(saturation * 100)}%, ${
lightness * 100
}%)`;
}
toString() {
return this.toHex();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment