guhh ororr

This commit is contained in:
sadan 2024-11-25 18:21:30 -05:00
parent 4202d264ec
commit 1f9d205bf2
No known key found for this signature in database

View file

@ -1,41 +1,281 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* Copyright (c) 2024 sadan
* SPDX-License-Identifier: GPL-3.0-or-later
*/
const clamp = (min: number, max: number) => (num: number) =>
Math.max(min, Math.min(max, num));
const clampContrast = clamp(1, 21);
const snap = (mult: number, num: number) =>
Math.floor((num % mult) / (mult / 2)) * mult + (num - (num % mult));
/**
* @param minContrast -- the min contrast to convert fgColor to
* @returns a css-valid string that is the repersenting color
* 0-1
*/
export function getContrastingColor(minContrast: number, fgColor: string, bgColor: string): string {
return "";
}/**
* @param color -- hex code with #
interface sRGB {
type: "srgb";
r: number;
g: number;
b: number;
}
interface lRGB {
type: "lrgb";
r: number;
g: number;
b: number;
}
interface HSL {
type: "hsl";
h: number;
/**
* 0-1
*/
export function lumin(color: string) {
const c: [number, number, number] = [0, 0, 0];
if (color.length === 4) {
c[0] = parseInt(color[1], 16);
c[1] = parseInt(color[2], 16);
c[2] = parseInt(color[3], 16);
} else if (color.length === 7) {
c[0] = parseInt(color.substring(1, 3), 16);
c[1] = parseInt(color.substring(3, 5), 16);
c[2] = parseInt(color.substring(5, 7), 16);
s: number;
/**
* 0-1
*/
l: number;
}
interface OKLAB {
type: "oklab";
l: number;
a: number;
b: number;
}
type AnyColor = sRGB | lRGB | HSL | OKLAB;
class Color {
private sRGB: sRGB;
private get lRGB(): lRGB {
return {
type: "lrgb",
r: this.sRGB.r <= 0.03928 ? this.sRGB.r / 12.92 : ((this.sRGB.r + 0.055) / 1.055) ** 2.4,
g: this.sRGB.g <= 0.03928 ? this.sRGB.g / 12.92 : ((this.sRGB.g + 0.055) / 1.055) ** 2.4,
b: this.sRGB.b <= 0.03928 ? this.sRGB.b / 12.92 : ((this.sRGB.b + 0.055) / 1.055) ** 2.4,
};
}
private get HSL(): HSL {
const cmin = Math.min(this.sRGB.r, this.sRGB.g, this.sRGB.b);
const cmax = Math.max(this.sRGB.r, this.sRGB.g, this.sRGB.b);
const delta = cmax - cmin;
let h = 0;
let s = 0;
let l = (cmax + cmin) / 2;
if (delta === 0) {
s = 0;
l = 0;
} else {
throw new Error("invalid color");
}
c.map(x => x / 255).map(x => x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4);
s = delta / (1 - Math.abs(2 * l - 1));
return (0.2126 * c[0]) + (0.7152 * c[1]) + (0.0722 * c[2]);
switch (cmax) {
case this.sRGB.r: {
h = ((this.sRGB.g - this.sRGB.b) / delta + (this.sRGB.g < this.sRGB.b ? 6 : 0)) % 6;
break;
}
case this.sRGB.g: {
h = (this.sRGB.b - this.sRGB.r) / delta + 2;
break;
}
case this.sRGB.b: {
h = (this.sRGB.r - this.sRGB.g) / delta + 4;
break;
}
}
h = Math.round(h * 60);
}
return {
type: "hsl",
h,
s,
l,
};
}
private get OKLAB(): OKLAB {
const { r, g, b } = this.lRGB;
let l = 0.4121656120 * r + 0.5362752080 * g + 0.0514575653 * b;
let m = 0.2118591070 * r + 0.6807189570 * g + 0.1074065790 * b;
let s = 0.0883097947 * r + 0.2818474170 * g + 0.6302613616 * b;
l = Math.cbrt(l);
m = Math.cbrt(m);
s = Math.cbrt(s);
return {
type: "oklab",
l: 0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s,
a: 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s,
b: 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s,
};
}
private get lumin(): number {
return (
0.2126 * this.lRGB.r + 0.7152 * this.lRGB.g + 0.0722 * this.lRGB.b
);
}
public get rbgString(): string {
return `rgb(${this.sRGB.r * 255}, ${this.sRGB.g * 255}, ${this.sRGB.b * 255})`;
}
public get hslString(): string {
return `hsl(${this.HSL.h}, ${(this.HSL.s * 100).toFixed(1)}%, ${(this.HSL.l * 100).toFixed(1)}%)`;
}
public get lightness(): number {
return this.HSL.l;
}
private constructor(c: sRGB | HSL | OKLAB) {
switch (c.type) {
case "srgb":
this.sRGB = c;
break;
case "oklab": {
this.sRGB = Color.lRGBtosRGB(Color.OKLABtolRGB(c));
break;
}
case "hsl":
this.sRGB = Color.HSLtosRGB(c);
break;
}
}
public static fromHex(color: string): Color {
return new Color(Color.hexToRGB(color));
}
public static contrast(fg: Color, bg: Color): number {
return (fg.lumin + 0.05) / (bg.lumin + 0.05);
}
public mix(colorspace: "oklab", thisPercent: number, other: Color, otherPercent = 1 - thisPercent): Color {
switch (colorspace) {
case "oklab": {
const okl1 = this.OKLAB;
const okl2 = other.OKLAB;
if (thisPercent + otherPercent !== 1) {
throw new Error("percentages must add up to 1");
}
const mixedOKLAB: OKLAB = {
type: "oklab",
l: okl1.l * thisPercent + okl2.l * otherPercent,
a: okl1.a * thisPercent + okl2.a * otherPercent,
b: okl1.b * thisPercent + okl2.b * otherPercent,
};
return new Color(mixedOKLAB);
}
}
throw new Error("unsupported colorspace: " + colorspace);
}
public bumpLightness(amount: number): Color {
return new Color({
type: "hsl",
h: this.HSL.h,
s: this.HSL.s,
l: clamp(0, 1)(this.HSL.l + amount),
});
}
private static OKLABtolRGB({ l, a, b }: OKLAB): lRGB {
const l1 = Math.pow(l + 0.3963377774 * a + 0.2158037573 * b, 3);
const m1 = Math.pow(l - 0.1055613458 * a - 0.0638541728 * b, 3);
const s1 = Math.pow(l - 0.0894841775 * a - 1.2914855480 * b, 3);
return {
type: "lrgb",
r: 4.0767416621 * l1 - 3.3077115913 * m1 + 0.2309699292 * s1,
g: -1.2684380046 * l1 + 2.6097574011 * m1 - 0.3413193965 * s1,
b: -0.0041960863 * l1 - 0.7034186147 * m1 + 1.7076147010 * s1,
};
}
private static lRGBtosRGB({ r, g, b }: lRGB): sRGB {
// Apply gamma correction to each channel
const sr =
r <= 0.0031308 ? 12.92 * r : 1.055 * Math.pow(r, 1.0 / 2.4) - 0.055;
const sg =
g <= 0.0031308 ? 12.92 * g : 1.055 * Math.pow(g, 1.0 / 2.4) - 0.055;
const sb =
b <= 0.0031308 ? 12.92 * b : 1.055 * Math.pow(b, 1.0 / 2.4) - 0.055;
return {
type: "srgb",
r: sr,
g: sg,
b: sb,
};
}
private static HSLtosRGB({ h, s, l }: HSL): sRGB {
const k = n => (n + h / 30) % 12;
const a = s * Math.min(l, 1 - l);
const f = n => l - a * Math.max(Math.min(k(n) - 3, 9 - k(n), 1), -1);
const r = f(0);
const g = f(8);
const b = f(4);
return {
type: "srgb",
r, g, b
};
}
private static hexToRGB(color: string): sRGB {
color = color.replace("#", "");
let c: [number, number, number] = [0, 0, 0];
if (color.length === 3) {
c[0] = parseInt(color[0], 16);
c[1] = parseInt(color[1], 16);
c[2] = parseInt(color[2], 16);
} else if (color.length === 6) {
c[0] = parseInt(color.substring(0, 2), 16);
c[1] = parseInt(color.substring(2, 4), 16);
c[2] = parseInt(color.substring(4, 6), 16);
} else {
throw new Error("invalid color: " + color);
}
// @ts-expect-error
c = c.map(x => x / 255);
return {
type: "srgb",
r: c[0],
g: c[1],
b: c[2],
};
}
}
class Contrast {
public constructor(private fg: Color, private bg: Color) { }
private ratio(c: Color) {
return Color.contrast(c, this.bg);
}
public calculateMinContrastColor(contrast: number, step: number): string {
step = Math.abs(step);
step = this.bg.lightness > 0.5 ? -step : step;
const snapStep = snap.bind(null, step);
contrast = clampContrast(contrast);
contrast = snapStep(contrast);
const startingContrast = this.ratio(this.fg);
if (startingContrast >= contrast) return this.fg.rbgString;
let currentColor: Color = this.fg;
let tries =
(snapStep(this.bg.lightness) - snapStep(this.fg.lightness)) / step +
(Math.abs(.5 - snapStep(this.bg.lightness)) + .5) / Math.abs(step);
while (this.ratio(currentColor) <= contrast && tries--) {
currentColor = currentColor.bumpLightness(step);
if (this.ratio(currentColor) >= contrast) {
break;
}
}
return currentColor.rbgString;
}
// https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-procedure
/**
* @param color1 -- hex code, with #
* @param color2 -- hex code, with #
*/
function calculateContrast(color1: string, color2: string) {
return (lumin(color1) + 0.05) / (lumin(color2) + 0.05);
}