mirror of
https://github.com/Vendicated/Vencord.git
synced 2025-02-24 07:25:10 +00:00
guhh ororr
This commit is contained in:
parent
4202d264ec
commit
1f9d205bf2
1 changed files with 271 additions and 31 deletions
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue