Telegram Web K with changes to work inside I2P
https://web.telegram.i2p/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
266 lines
7.7 KiB
266 lines
7.7 KiB
/* |
|
* https://github.com/morethanwords/tweb |
|
* Copyright (C) 2019-2021 Eduard Kuzmenko |
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE |
|
*/ |
|
|
|
import type {WallPaper} from '../layer'; |
|
import clamp from './number/clamp'; |
|
|
|
export type ColorHsla = { |
|
h: number, |
|
s: number, |
|
l: number, |
|
a: number |
|
}; |
|
|
|
export type ColorRgba = [number, number, number, number]; |
|
export type ColorRgb = [number, number, number]; |
|
|
|
/** |
|
* https://stackoverflow.com/a/54070620/6758968 |
|
* r, g, b in [0, 255] |
|
* @returns h in [0,360) and s, v in [0,1] |
|
*/ |
|
export function rgbToHsv(r: number, g: number, b: number): [number, number, number] { |
|
r /= 255, g /= 255, b /= 255; |
|
const v = Math.max(r, g, b), |
|
c = v - Math.min(r, g, b); |
|
const h = c && ((v === r) ? (g - b ) / c : ((v == g) ? 2 + (b - r) / c : 4 + (r - g) / c)); |
|
return [60 * (h < 0 ? h + 6 : h), v && c / v, v]; |
|
} |
|
|
|
/** |
|
* https://stackoverflow.com/a/54024653/6758968 |
|
* @param h [0, 360] |
|
* @param s [0, 1] |
|
* @param v [0, 1] |
|
* @returns r, g, b in [0, 255] |
|
*/ |
|
export function hsvToRgb(h: number, s: number, v: number): ColorRgb { |
|
const f = (n: number, k: number = (n + h / 60) % 6) => Math.round((v - v * s * Math.max(Math.min(k, 4 - k, 1), 0)) * 255); |
|
return [f(5), f(3), f(1)]; |
|
} |
|
|
|
/** |
|
* @returns h [0, 360], s [0, 100], l [0, 100], a [0, 1] |
|
*/ |
|
export function rgbaToHsla(r: number, g: number, b: number, a: number = 1): ColorHsla { |
|
r /= 255, g /= 255, b /= 255; |
|
const max = Math.max(r, g, b), |
|
min = Math.min(r, g, b); |
|
let h: number, s: number; |
|
const l = (max + min) / 2; |
|
|
|
if(max === min) { |
|
h = s = 0; // achromatic |
|
} else { |
|
const d = max - min; |
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min); |
|
switch(max) { |
|
case r: |
|
h = (g - b) / d + (g < b ? 6 : 0); |
|
break; |
|
case g: |
|
h = (b - r) / d + 2; |
|
break; |
|
case b: |
|
h = (r - g) / d + 4; |
|
break; |
|
} |
|
h /= 6; |
|
} |
|
|
|
return { |
|
h: h * 360, |
|
s: s * 100, |
|
l: l * 100, |
|
a |
|
}; |
|
} |
|
|
|
// * https://stackoverflow.com/a/9493060/6758968 |
|
/** |
|
* Converts an HSL color value to RGB. Conversion formula |
|
* adapted from http://en.wikipedia.org/wiki/HSL_color_space. |
|
* |
|
* @param {number} h The hue [0, 360] |
|
* @param {number} s The saturation [0, 1] |
|
* @param {number} l The lightness [0, 1] |
|
* @return {Array} The RGB representation [0, 255] |
|
*/ |
|
export function hslaToRgba(h: number, s: number, l: number, a: number): ColorRgba { |
|
h /= 360, s /= 100, l /= 100; |
|
let r: number, g: number, b: number; |
|
|
|
if(s === 0) { |
|
r = g = b = l; // achromatic |
|
} else { |
|
const hue2rgb = function hue2rgb(p: number, q: number, t: number) { |
|
if(t < 0) t += 1; |
|
if(t > 1) t -= 1; |
|
if(t < 1/6) return p + (q - p) * 6 * t; |
|
if(t < 1/2) return q; |
|
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; |
|
return p; |
|
} |
|
|
|
const q = l < 0.5 ? l * (1 + s) : l + s - l * s; |
|
const p = 2 * l - q; |
|
r = hue2rgb(p, q, h + 1/3); |
|
g = hue2rgb(p, q, h); |
|
b = hue2rgb(p, q, h - 1/3); |
|
} |
|
|
|
return [r, g, b, a].map((v) => Math.round(v * 255)) as ColorRgba; |
|
} |
|
|
|
export function hslaStringToRgba(hsla: string) { |
|
const splitted = hsla.slice(5, -1).split(', '); |
|
const alpha = +splitted.pop(); |
|
const arr = splitted.map((val) => { |
|
if(val.endsWith('%')) { |
|
return +val.slice(0, -1); |
|
} |
|
|
|
return +val; |
|
}); |
|
|
|
return hslaToRgba(arr[0], arr[1], arr[2], alpha); |
|
} |
|
|
|
export function hexaToRgba(hexa: string) { |
|
const arr: ColorRgba = [] as any; |
|
const offset = hexa[0] === '#' ? 1 : 0; |
|
if(hexa.length === (5 + offset)) { |
|
hexa = (offset ? '#' : '') + '0' + hexa.slice(offset); |
|
} |
|
|
|
if(hexa.length === (3 + offset)) { |
|
for(let i = offset; i < hexa.length; ++i) { |
|
arr.push(parseInt(hexa[i] + hexa[i], 16)); |
|
} |
|
} else if(hexa.length === (4 + offset)) { |
|
for(let i = offset; i < (hexa.length - 1); ++i) { |
|
arr.push(parseInt(hexa[i] + hexa[i], 16)); |
|
} |
|
|
|
arr.push(parseInt(hexa[hexa.length - 1], 16)); |
|
} else { |
|
for(let i = offset; i < hexa.length; i += 2) { |
|
arr.push(parseInt(hexa.slice(i, i + 2), 16)); |
|
} |
|
} |
|
|
|
return arr; |
|
} |
|
|
|
export function hexToRgb(hex: string) { |
|
return hexaToRgba(hex.slice(0, 7)) as any as ColorRgb; |
|
} |
|
|
|
export function hexaToHsla(hexa: string) { |
|
const rgba = hexaToRgba(hexa); |
|
return rgbaToHsla(rgba[0], rgba[1], rgba[2], rgba[3]); |
|
} |
|
|
|
export function rgbaToHexa(rgba: ColorRgba | ColorRgb) { |
|
return '#' + rgba.map((v) => ('0' + v.toString(16)).slice(-2)).join(''); |
|
} |
|
|
|
export function hslaStringToHexa(hsla: string) { |
|
return rgbaToHexa(hslaStringToRgba(hsla)); |
|
} |
|
|
|
export function hslaStringToHex(hsla: string) { |
|
return hslaStringToHexa(hsla).slice(0, -2); |
|
} |
|
|
|
/** |
|
* @param weight [0, 1] |
|
*/ |
|
export function mixColors(color1: ColorRgb, color2: ColorRgb, weight: number) { |
|
const out = new Array<number>(3) as ColorRgb; |
|
for(let i = 0; i < 3; ++i) { |
|
const v1 = color1[i], v2 = color2[i]; |
|
out[i] = Math.floor(v2 + (v1 - v2) * weight); |
|
} |
|
|
|
return out; |
|
} |
|
|
|
export function computePerceivedBrightness(color: ColorRgb) { |
|
return (color[0] * 0.2126 + color[1] * 0.7152 + color[2] * 0.0722) / 255; |
|
} |
|
|
|
export function getAverageColor(color1: ColorRgb, color2: ColorRgb): ColorRgb { |
|
return color1.map((v, i) => Math.round((v + color2[i]) / 2)) as ColorRgb; |
|
} |
|
|
|
export function getAccentColor(baseHsv: number[], baseColor: ColorRgb, elementColor: ColorRgb): ColorRgb { |
|
const hsvTemp3 = rgbToHsv(...baseColor); |
|
const hsvTemp4 = rgbToHsv(...elementColor); |
|
|
|
const dist = Math.min(1.5 * hsvTemp3[1] / baseHsv[1], 1); |
|
|
|
hsvTemp3[0] = Math.min(360, hsvTemp4[0] - hsvTemp3[0] + baseHsv[0]); |
|
hsvTemp3[1] = Math.min(1, hsvTemp4[1] * baseHsv[1] / hsvTemp3[1]); |
|
hsvTemp3[2] = Math.min(1, (hsvTemp4[2] / hsvTemp3[2] + dist - 1) * baseHsv[2] / dist); |
|
if(hsvTemp3[2] < 0.3) { |
|
return elementColor; |
|
} |
|
return hsvToRgb(...hsvTemp3); |
|
} |
|
|
|
export function changeColorAccent(baseHsv: number[], accentHsv: number[], color: ColorRgb, isDarkTheme: boolean) { |
|
const colorHsv = rgbToHsv(...color); |
|
|
|
const diffH = Math.min(Math.abs(colorHsv[0] - baseHsv[0]), Math.abs(colorHsv[0] - baseHsv[0] - 360)); |
|
if(diffH > 30) { |
|
return color; |
|
} |
|
|
|
const dist = Math.min(1.5 * colorHsv[1] / baseHsv[1], 1); |
|
|
|
colorHsv[0] = Math.min(360, colorHsv[0] + accentHsv[0] - baseHsv[0]); |
|
colorHsv[1] = Math.min(1, colorHsv[1] * accentHsv[1] / baseHsv[1]); |
|
colorHsv[2] = Math.min(1, colorHsv[2] * (1 - dist + dist * accentHsv[2] / baseHsv[2])); |
|
|
|
let newColor = hsvToRgb(...colorHsv); |
|
|
|
const origBrightness = computePerceivedBrightness(color); |
|
const newBrightness = computePerceivedBrightness(newColor); |
|
|
|
// We need to keep colors lighter in dark themes and darker in light themes |
|
const needRevertBrightness = isDarkTheme ? origBrightness > newBrightness : origBrightness < newBrightness; |
|
|
|
if(needRevertBrightness) { |
|
const amountOfNew = 0.6; |
|
const fallbackAmount = (1 - amountOfNew) * origBrightness / newBrightness + amountOfNew; |
|
newColor = changeBrightness(newColor, fallbackAmount); |
|
} |
|
|
|
return newColor; |
|
} |
|
|
|
export function changeBrightness(color: ColorRgb, amount: number) { |
|
return color.map((v) => clamp(Math.round(v * amount), 0, 255)) as ColorRgb; |
|
} |
|
|
|
export function getHexColorFromTelegramColor(color: number) { |
|
const hex = (color < 0 ? 0xFFFFFF + color : color).toString(16); |
|
return '#' + (hex.length >= 6 ? hex : '0'.repeat(6 - hex.length) + hex); |
|
} |
|
|
|
export function getRgbColorFromTelegramColor(color: number) { |
|
return hexToRgb(getHexColorFromTelegramColor(color)); |
|
} |
|
|
|
export function getColorsFromWallPaper(wallPaper: WallPaper) { |
|
return wallPaper.settings ? [ |
|
wallPaper.settings.background_color, |
|
wallPaper.settings.second_background_color, |
|
wallPaper.settings.third_background_color, |
|
wallPaper.settings.fourth_background_color |
|
].filter(Boolean).map(getHexColorFromTelegramColor).join(',') : ''; |
|
}
|
|
|