[Refactoring] Prettify color convertion utils
This commit is contained in:
parent
9ea1d5c710
commit
3fd03fbe61
@ -22,7 +22,7 @@ import type {
|
||||
MediaContent,
|
||||
} from '../../types';
|
||||
|
||||
import { numberToHexColor } from '../../../util/colors';
|
||||
import { int2hex } from '../../../util/colors';
|
||||
import { pick } from '../../../util/iteratees';
|
||||
import { toJSNumber } from '../../../util/numbers';
|
||||
import { addDocumentToLocalDb } from '../helpers/localDb';
|
||||
@ -380,10 +380,10 @@ export function buildApiBotInfo(botInfo: GramJs.BotInfo, chatId: string): ApiBot
|
||||
export function buildBotAppSettings(settings: GramJs.BotAppSettings): ApiBotAppSettings {
|
||||
const placeholderPath = settings.placeholderPath && buildSvgPath(settings.placeholderPath);
|
||||
return {
|
||||
backgroundColor: settings.backgroundColor ? numberToHexColor(settings.backgroundColor) : undefined,
|
||||
backgroundDarkColor: settings.backgroundDarkColor ? numberToHexColor(settings.backgroundDarkColor) : undefined,
|
||||
headerColor: settings.headerColor ? numberToHexColor(settings.headerColor) : undefined,
|
||||
headerDarkColor: settings.headerDarkColor ? numberToHexColor(settings.headerDarkColor) : undefined,
|
||||
backgroundColor: settings.backgroundColor ? int2hex(settings.backgroundColor) : undefined,
|
||||
backgroundDarkColor: settings.backgroundDarkColor ? int2hex(settings.backgroundDarkColor) : undefined,
|
||||
headerColor: settings.headerColor ? int2hex(settings.headerColor) : undefined,
|
||||
headerDarkColor: settings.headerDarkColor ? int2hex(settings.headerDarkColor) : undefined,
|
||||
placeholderPath,
|
||||
};
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ import type {
|
||||
ApiTypeResaleStarGifts,
|
||||
} from '../../types';
|
||||
|
||||
import { numberToHexColor } from '../../../util/colors';
|
||||
import { int2hex } from '../../../util/colors';
|
||||
import { toJSNumber } from '../../../util/numbers';
|
||||
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||
import { addDocumentToLocalDb } from '../helpers/localDb';
|
||||
@ -131,10 +131,10 @@ export function buildApiStarGiftAttribute(attribute: GramJs.TypeStarGiftAttribut
|
||||
backdropId,
|
||||
name,
|
||||
rarityPercent: rarityPermille / 10,
|
||||
centerColor: numberToHexColor(centerColor),
|
||||
edgeColor: numberToHexColor(edgeColor),
|
||||
patternColor: numberToHexColor(patternColor),
|
||||
textColor: numberToHexColor(textColor),
|
||||
centerColor: int2hex(centerColor),
|
||||
edgeColor: int2hex(edgeColor),
|
||||
patternColor: int2hex(patternColor),
|
||||
textColor: int2hex(textColor),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ import type {
|
||||
} from '../../types';
|
||||
|
||||
import { CHANNEL_ID_BASE } from '../../../config';
|
||||
import { numberToHexColor } from '../../../util/colors';
|
||||
import { int2hex } from '../../../util/colors';
|
||||
import { buildCollectionByCallback } from '../../../util/iteratees';
|
||||
|
||||
type TypePeerOrInput = GramJs.TypePeer | GramJs.TypeInputPeer | GramJs.TypeInputUser | GramJs.TypeInputChannel;
|
||||
@ -59,14 +59,14 @@ export function buildApiPeerColor(peerColor: GramJs.TypePeerColor): ApiPeerColor
|
||||
}
|
||||
|
||||
function buildApiPeerColorSet(colorSet: GramJs.help.PeerColorSet) {
|
||||
return colorSet.colors.map((color) => numberToHexColor(color));
|
||||
return colorSet.colors.map((color) => int2hex(color));
|
||||
}
|
||||
|
||||
function buildApiPeerProfileColorSet(colorSet: GramJs.help.PeerColorProfileSet): ApiPeerProfileColorSet {
|
||||
return {
|
||||
paletteColors: colorSet.paletteColors.map((color) => numberToHexColor(color)),
|
||||
bgColors: colorSet.bgColors.map((color) => numberToHexColor(color)),
|
||||
storyColors: colorSet.storyColors.map((color) => numberToHexColor(color)),
|
||||
paletteColors: colorSet.paletteColors.map((color) => int2hex(color)),
|
||||
bgColors: colorSet.bgColors.map((color) => int2hex(color)),
|
||||
storyColors: colorSet.storyColors.map((color) => int2hex(color)),
|
||||
};
|
||||
}
|
||||
|
||||
@ -116,10 +116,10 @@ ApiEmojiStatusType | undefined {
|
||||
title: mtpEmojiStatus.title,
|
||||
slug: mtpEmojiStatus.slug,
|
||||
patternDocumentId: mtpEmojiStatus.patternDocumentId.toString(),
|
||||
centerColor: numberToHexColor(mtpEmojiStatus.centerColor),
|
||||
edgeColor: numberToHexColor(mtpEmojiStatus.edgeColor),
|
||||
patternColor: numberToHexColor(mtpEmojiStatus.patternColor),
|
||||
textColor: numberToHexColor(mtpEmojiStatus.textColor),
|
||||
centerColor: int2hex(mtpEmojiStatus.centerColor),
|
||||
edgeColor: int2hex(mtpEmojiStatus.edgeColor),
|
||||
patternColor: int2hex(mtpEmojiStatus.patternColor),
|
||||
textColor: int2hex(mtpEmojiStatus.textColor),
|
||||
until: mtpEmojiStatus.until,
|
||||
};
|
||||
}
|
||||
|
||||
@ -15,8 +15,8 @@ import { ensureRLottie, getRLottie } from '../../lib/rlottie/RLottie.async';
|
||||
import { IS_TAURI } from '../../util/browser/globalEnvironment';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import buildStyle from '../../util/buildStyle';
|
||||
import { hex2rgbaObj } from '../../util/colors.ts';
|
||||
import generateUniqueId from '../../util/generateUniqueId';
|
||||
import { hexToRgb } from '../../util/switchTheme';
|
||||
|
||||
import useColorFilter from '../../hooks/stickers/useColorFilter';
|
||||
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
|
||||
@ -116,7 +116,7 @@ const AnimatedSticker: FC<OwnProps> = ({
|
||||
|
||||
useSyncEffect(() => {
|
||||
if (color && !shouldUseColorFilter) {
|
||||
const { r, g, b } = hexToRgb(color);
|
||||
const { r, g, b } = hex2rgbaObj(color);
|
||||
rgbColor.current = [r, g, b];
|
||||
} else {
|
||||
rgbColor.current = undefined;
|
||||
|
||||
@ -6,8 +6,8 @@ import { requestMutation } from '../../../lib/fasterdom/fasterdom';
|
||||
import { getStickerMediaHash } from '../../../global/helpers';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import buildStyle from '../../../util/buildStyle';
|
||||
import { hex2rgba, rgba2hex } from '../../../util/colors.ts';
|
||||
import { preloadImage } from '../../../util/files';
|
||||
import { hexToRgb } from '../../../util/switchTheme.ts';
|
||||
import { REM } from '../helpers/mediaDimensions';
|
||||
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
@ -201,17 +201,12 @@ export default memo(RadialPatternBackground);
|
||||
|
||||
function adjustBrightness(hex: string, delta: number) {
|
||||
const factor = 1 + delta;
|
||||
const rgba = hexToRgb(hex);
|
||||
const darkenedRgba = [
|
||||
Math.min(255, Math.round(rgba.r * factor)),
|
||||
Math.min(Math.round(rgba.g * factor)),
|
||||
Math.min(Math.round(rgba.b * factor)),
|
||||
rgba.a ?? 1,
|
||||
] as const;
|
||||
const [r, g, b, a] = hex2rgba(hex);
|
||||
|
||||
return rgbaToHex(...darkenedRgba);
|
||||
}
|
||||
|
||||
function rgbaToHex(r: number, g: number, b: number, a: number) {
|
||||
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}${Math.round(a * 255).toString(16)}`;
|
||||
return rgba2hex([
|
||||
Math.min(255, Math.round(r * factor)),
|
||||
Math.min(255, Math.round(g * factor)),
|
||||
Math.min(255, Math.round(b * factor)),
|
||||
a ?? 255,
|
||||
]);
|
||||
}
|
||||
|
||||
@ -99,10 +99,12 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps> = ({
|
||||
const currentWallpaper = loadedWallpapers && loadedWallpapers.find((wallpaper) => wallpaper.slug === slug);
|
||||
if (currentWallpaper?.document.thumbnail) {
|
||||
getAverageColor(currentWallpaper.document.thumbnail.dataUri)
|
||||
.then((color) => {
|
||||
const patternColor = getPatternColor(color);
|
||||
const rgbColor = `#${rgb2hex(color)}`;
|
||||
setThemeSettings({ theme: themeRef.current!, backgroundColor: rgbColor, patternColor });
|
||||
.then((averageColor) => {
|
||||
setThemeSettings({
|
||||
theme: themeRef.current!,
|
||||
backgroundColor: rgb2hex(averageColor),
|
||||
patternColor: getPatternColor(averageColor),
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [loadedWallpapers, setThemeSettings]);
|
||||
|
||||
@ -13,7 +13,7 @@ import { selectTheme, selectThemeValues } from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { captureEvents } from '../../../util/captureEvents';
|
||||
import {
|
||||
getPatternColor, hex2rgb, hsb2rgb, rgb2hex, rgb2hsb,
|
||||
getPatternColor, hex2rgb, hsv2rgb, rgb2hex, rgb2hsv,
|
||||
} from '../../../util/colors';
|
||||
import { pick } from '../../../util/iteratees';
|
||||
|
||||
@ -47,7 +47,7 @@ interface CanvasRects {
|
||||
};
|
||||
}
|
||||
|
||||
const DEFAULT_HSB = rgb2hsb(hex2rgb('e6ebee'));
|
||||
const DEFAULT_HSV = rgb2hsv(hex2rgb('e6ebee'));
|
||||
const PREDEFINED_COLORS = [
|
||||
'#e6ebee', '#b2cee1', '#008dd0', '#c6e7cb', '#c4e1a6', '#60b16e',
|
||||
'#ccd0af', '#a6a997', '#7a7072', '#fdd7af', '#fdb76e', '#dd8851',
|
||||
@ -68,12 +68,12 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps> = ({
|
||||
const huePickerRef = useRef<HTMLDivElement>();
|
||||
const isFirstRunRef = useRef(true);
|
||||
|
||||
const [hsb, setHsb] = useState(getInitialHsb(backgroundColor));
|
||||
const [hsv, setHsv] = useState(getInitialHsv(backgroundColor));
|
||||
// Cache for drag handlers
|
||||
const hsbRef = useRef(hsb);
|
||||
const hsvRef = useRef(hsv);
|
||||
useEffect(() => {
|
||||
hsbRef.current = hsb;
|
||||
}, [hsb]);
|
||||
hsvRef.current = hsv;
|
||||
}, [hsv]);
|
||||
|
||||
const [isDragging, markIsDragging, unmarkIsDragging] = useFlag();
|
||||
const [rgbInput, setRgbInput] = useState('');
|
||||
@ -102,9 +102,9 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps> = ({
|
||||
Math.min(Math.max(0, e.pageY! - colorRect.top + containerRef.current!.scrollTop), colorRect.height - 1),
|
||||
];
|
||||
|
||||
const { huePosition } = hsb2positions(hsbRef.current, rectsRef.current!);
|
||||
const { huePosition } = hsv2positions(hsvRef.current, rectsRef.current!);
|
||||
|
||||
setHsb(positions2hsb({ colorPosition, huePosition }, rectsRef.current!));
|
||||
setHsv(positions2hsv({ colorPosition, huePosition }, rectsRef.current!));
|
||||
markIsDragging();
|
||||
|
||||
return true;
|
||||
@ -120,10 +120,10 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
|
||||
function handleHueDrag(e: MouseEvent | RealTouchEvent) {
|
||||
const { colorPosition } = hsb2positions(hsbRef.current, rectsRef.current!);
|
||||
const { colorPosition } = hsv2positions(hsvRef.current, rectsRef.current!);
|
||||
const huePosition = Math.min(Math.max(0, e.pageX! - hueRect.offsetLeft), hueRect.width - 1);
|
||||
|
||||
setHsb(positions2hsb({ colorPosition, huePosition }, rectsRef.current!));
|
||||
setHsv(positions2hsv({ colorPosition, huePosition }, rectsRef.current!));
|
||||
markIsDragging();
|
||||
|
||||
return true;
|
||||
@ -139,15 +139,15 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
}, [markIsDragging, unmarkIsDragging]);
|
||||
|
||||
const { colorPosition = [0, 0], huePosition = 0 } = rectsRef.current ? hsb2positions(hsb, rectsRef.current) : {};
|
||||
const hex = rgb2hex(hsb2rgb(hsb));
|
||||
const hue = hsb[0];
|
||||
const hueHex = rgb2hex(hsb2rgb([hue, 1, 1]));
|
||||
const { colorPosition = [0, 0], huePosition = 0 } = rectsRef.current ? hsv2positions(hsv, rectsRef.current) : {};
|
||||
const hex = rgb2hex(hsv2rgb(hsv));
|
||||
const hue = hsv[0];
|
||||
const hueHex = rgb2hex(hsv2rgb([hue, 1, 1]));
|
||||
|
||||
// Save value and update inputs when HSL changes
|
||||
useEffect(() => {
|
||||
const rgb = hsb2rgb(hsb);
|
||||
const color = `#${rgb2hex(rgb)}`;
|
||||
const rgb = hsv2rgb(hsv);
|
||||
const color = rgb2hex(rgb);
|
||||
|
||||
setRgbInput(rgb.join(', '));
|
||||
setHexInput(color);
|
||||
@ -162,7 +162,7 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
}
|
||||
isFirstRunRef.current = false;
|
||||
}, [hsb, setThemeSettings]);
|
||||
}, [hsv, setThemeSettings]);
|
||||
|
||||
// Redraw color picker when hue changes
|
||||
useEffect(() => {
|
||||
@ -179,7 +179,7 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps> = ({
|
||||
|
||||
if (rgbValue.match(/^\d{1,3},\s?\d{1,3},\s?\d{1,3}$/)) {
|
||||
const rgb = rgbValue.split(',').map((channel) => Number(channel.trim())) as [number, number, number];
|
||||
setHsb(rgb2hsb(rgb));
|
||||
setHsv(rgb2hsv(rgb));
|
||||
}
|
||||
|
||||
e.currentTarget.value = rgbValue;
|
||||
@ -189,14 +189,14 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps> = ({
|
||||
const hexValue = e.currentTarget.value.replace(/[^0-9a-fA-F]/g, '').slice(0, 6);
|
||||
|
||||
if (hexValue.match(/^#?[0-9a-fA-F]{6}$/)) {
|
||||
setHsb(rgb2hsb(hex2rgb(hexValue.replace('#', ''))));
|
||||
setHsv(rgb2hsv(hex2rgb(hexValue)));
|
||||
}
|
||||
|
||||
e.currentTarget.value = hexValue;
|
||||
}, []);
|
||||
|
||||
const handlePredefinedColorClick = useCallback((e: React.MouseEvent<HTMLInputElement>) => {
|
||||
setHsb(rgb2hsb(hex2rgb(e.currentTarget.dataset.color!.replace('#', ''))));
|
||||
setHsv(rgb2hsv(hex2rgb(e.currentTarget.dataset.color!)));
|
||||
}, []);
|
||||
|
||||
const className = buildClassName(
|
||||
@ -216,14 +216,14 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps> = ({
|
||||
<canvas />
|
||||
<div
|
||||
className="handle"
|
||||
style={`transform: translate(${colorPosition[0]}px, ${colorPosition[1]}px); background-color: #${hex};`}
|
||||
style={`transform: translate(${colorPosition[0]}px, ${colorPosition[1]}px); background-color: ${hex};`}
|
||||
/>
|
||||
</div>
|
||||
<div ref={huePickerRef} className="hue-picker">
|
||||
<canvas />
|
||||
<div
|
||||
className="handle"
|
||||
style={`transform: translateX(${huePosition}px); background-color: #${hueHex};`}
|
||||
style={`transform: translateX(${huePosition}px); background-color: ${hueHex};`}
|
||||
/>
|
||||
</div>
|
||||
<div className="tools">
|
||||
@ -234,7 +234,7 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps> = ({
|
||||
<div className="predefined-colors">
|
||||
{PREDEFINED_COLORS.map((color) => (
|
||||
<div
|
||||
className={buildClassName('predefined-color', color === `#${hex}` ? 'active' : undefined)}
|
||||
className={buildClassName('predefined-color', color === hex ? 'active' : undefined)}
|
||||
data-color={color}
|
||||
style={`background-color: ${color};`}
|
||||
onClick={handlePredefinedColorClick}
|
||||
@ -245,23 +245,23 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
function getInitialHsb(backgroundColor?: string) {
|
||||
return backgroundColor && backgroundColor.startsWith('#')
|
||||
? rgb2hsb(hex2rgb(backgroundColor.replace('#', '')))
|
||||
: DEFAULT_HSB;
|
||||
function getInitialHsv(backgroundColor?: string) {
|
||||
return backgroundColor?.startsWith('#')
|
||||
? rgb2hsv(hex2rgb(backgroundColor))
|
||||
: DEFAULT_HSV;
|
||||
}
|
||||
|
||||
function hsb2positions(hsb: [number, number, number], rects: CanvasRects) {
|
||||
function hsv2positions(hsv: [number, number, number], rects: CanvasRects) {
|
||||
return {
|
||||
colorPosition: [
|
||||
Math.round((hsb[1]) * (rects.colorRect.width - 1)),
|
||||
Math.round((1 - hsb[2]) * (rects.colorRect.height - 1)),
|
||||
Math.round((hsv[1]) * (rects.colorRect.width - 1)),
|
||||
Math.round((1 - hsv[2]) * (rects.colorRect.height - 1)),
|
||||
],
|
||||
huePosition: Math.round(hsb[0] * (rects.hueRect.width - 1)),
|
||||
huePosition: Math.round(hsv[0] * (rects.hueRect.width - 1)),
|
||||
};
|
||||
}
|
||||
|
||||
function positions2hsb(
|
||||
function positions2hsv(
|
||||
{ colorPosition, huePosition }: { colorPosition: number[]; huePosition: number },
|
||||
rects: CanvasRects,
|
||||
): [number, number, number] {
|
||||
@ -300,7 +300,7 @@ function drawColor(
|
||||
|
||||
const imgData = ctx!.createImageData(w, h);
|
||||
const pixels = imgData.data;
|
||||
const col = hsb2rgb([hue, 1, 1]);
|
||||
const col = hsv2rgb([hue, 1, 1]);
|
||||
|
||||
let index = 0;
|
||||
|
||||
@ -334,7 +334,7 @@ function drawHue(canvas: HTMLCanvasElement) {
|
||||
|
||||
for (let x = 0; x < w; x++) {
|
||||
const hue = x / (w - 1);
|
||||
const rgb = hsb2rgb([hue, 1, 1]);
|
||||
const rgb = hsv2rgb([hue, 1, 1]);
|
||||
|
||||
pixels[index++] = rgb[0];
|
||||
pixels[index++] = rgb[1];
|
||||
|
||||
@ -2,7 +2,7 @@ import { memo } from '../../../lib/teact/teact';
|
||||
|
||||
import type { IconName } from '../../../types/icons';
|
||||
|
||||
import { hexToRgb, lerpRgb } from '../../../util/switchTheme';
|
||||
import { hex2rgbaObj, lerpRgbaObj } from '../../../util/colors.ts';
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
@ -30,7 +30,7 @@ type OwnProps<T> = {
|
||||
const COLORS = [
|
||||
'#F2862D', '#EB7B4D', '#E46D72', '#DD6091', '#CC5FBA', '#B464E7',
|
||||
'#9873FF', '#768DFF', '#55A5FC', '#52B0C9', '#4FBC93', '#4CC663',
|
||||
].map(hexToRgb);
|
||||
].map(hex2rgbaObj);
|
||||
|
||||
const PremiumFeatureItem = <T,>({
|
||||
icon,
|
||||
@ -45,7 +45,7 @@ const PremiumFeatureItem = <T,>({
|
||||
const newIndex = (index / count) * COLORS.length;
|
||||
const colorA = COLORS[Math.floor(newIndex)];
|
||||
const colorB = COLORS[Math.ceil(newIndex)] ?? colorA;
|
||||
const { r, g, b } = lerpRgb(colorA, colorB, 0.5);
|
||||
const { r, g, b } = lerpRgbaObj(colorA, colorB, 0.5);
|
||||
|
||||
const handleClick = useLastCallback(() => {
|
||||
onClick?.(section);
|
||||
|
||||
@ -12,12 +12,12 @@ import { requestMeasure } from '../../../../lib/fasterdom/fasterdom';
|
||||
import { ensureRLottie } from '../../../../lib/rlottie/RLottie.async';
|
||||
import { selectCustomEmoji, selectIsAlwaysHighPriorityEmoji } from '../../../../global/selectors';
|
||||
import AbsoluteVideo from '../../../../util/AbsoluteVideo';
|
||||
import { hex2rgbaObj } from '../../../../util/colors.ts';
|
||||
import {
|
||||
addCustomEmojiInputRenderCallback,
|
||||
getCustomEmojiMediaDataForInput,
|
||||
} from '../../../../util/emoji/customEmojiManager';
|
||||
import { round } from '../../../../util/math';
|
||||
import { hexToRgb } from '../../../../util/switchTheme';
|
||||
import { REM } from '../../../common/helpers/mediaDimensions';
|
||||
|
||||
import useColorFilter from '../../../../hooks/stickers/useColorFilter';
|
||||
@ -230,7 +230,7 @@ async function createPlayer({
|
||||
colorFilter?: string;
|
||||
}): Promise<CustomEmojiPlayer> {
|
||||
if (customEmoji.isLottie) {
|
||||
const color = customEmoji.shouldUseTextColor && textColor ? hexToRgb(textColor) : undefined;
|
||||
const color = customEmoji.shouldUseTextColor && textColor ? hex2rgbaObj(textColor) : undefined;
|
||||
const RLottie = await ensureRLottie();
|
||||
const lottie = RLottie.init(
|
||||
mediaUrl,
|
||||
|
||||
@ -21,8 +21,7 @@ import {
|
||||
import { selectSharedSettings } from '../../../global/selectors/sharedState';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import buildStyle from '../../../util/buildStyle';
|
||||
import { getColorLuma } from '../../../util/colors';
|
||||
import { hexToRgb } from '../../../util/switchTheme';
|
||||
import { getColorLuma, hex2rgbaObj } from '../../../util/colors';
|
||||
import windowSize from '../../../util/windowSize';
|
||||
|
||||
import useInterval from '../../../hooks/schedulers/useInterval';
|
||||
@ -427,7 +426,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
const headerTextVar = useMemo(() => {
|
||||
if (isMoreAppsTabActive) return 'color-text';
|
||||
if (!headerColor) return undefined;
|
||||
const { r, g, b } = hexToRgb(headerColor);
|
||||
const { r, g, b } = hex2rgbaObj(headerColor);
|
||||
const luma = getColorLuma([r, g, b]);
|
||||
const adaptedLuma = theme === 'dark' ? 255 - luma : luma;
|
||||
return adaptedLuma > LUMA_THRESHOLD ? 'color-text' : 'color-background';
|
||||
|
||||
@ -12,7 +12,7 @@ import { requestForcedReflow, requestMutation } from '../../../lib/fasterdom/fas
|
||||
import { selectRestrictedEmoji } from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import buildStyle from '../../../util/buildStyle';
|
||||
import { convertToRGBA, getTextColor } from '../../../util/colors';
|
||||
import { getTextColor, int2cssRgba } from '../../../util/colors';
|
||||
import { formatTemperature } from '../../../util/formatTemperature';
|
||||
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
@ -48,7 +48,7 @@ const MediaAreaWeather: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const { temperatureC, color } = mediaArea;
|
||||
|
||||
const backgroundColor = convertToRGBA(color);
|
||||
const backgroundColor = int2cssRgba(color);
|
||||
const textColor = getTextColor(color);
|
||||
|
||||
const updateCustomSize = useLastCallback((isImmediate?: boolean) => {
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { useEffect } from '../../lib/teact/teact';
|
||||
|
||||
import { SVG_NAMESPACE } from '../../config';
|
||||
import { hex2rgbaObj } from '../../util/colors.ts';
|
||||
import { addSvgDefinition, removeSvgDefinition } from '../../util/svgController';
|
||||
import { hexToRgb } from '../../util/switchTheme';
|
||||
|
||||
const SVG_MAP = new Map<string, SvgColorFilter>();
|
||||
|
||||
@ -14,7 +14,7 @@ class SvgColorFilter {
|
||||
constructor(public color: string) {
|
||||
this.filterId = `color-filter-${color.slice(1)}`;
|
||||
|
||||
const rgbColor = hexToRgb(color);
|
||||
const rgbColor = hex2rgbaObj(color);
|
||||
addSvgDefinition(
|
||||
<filter color-interpolation-filters="sRGB" xmlns={SVG_NAMESPACE}>
|
||||
<feColorMatrix
|
||||
|
||||
@ -18,7 +18,7 @@ function useAverageColor(peer: ApiPeer, fallbackColor = '#00000000') {
|
||||
}
|
||||
|
||||
const averageColor = await getAverageColor(imgBlobUrl);
|
||||
setColor(`#${rgb2hex(averageColor)}`);
|
||||
setColor(rgb2hex(averageColor));
|
||||
})();
|
||||
}, [imgBlobUrl]);
|
||||
|
||||
|
||||
@ -1,32 +1,57 @@
|
||||
/* eslint-disable prefer-const */
|
||||
|
||||
import { preloadImage } from './files';
|
||||
import { lerp } from './math.ts';
|
||||
|
||||
const LUMA_THRESHOLD = 128;
|
||||
|
||||
type Number3 = [number, number, number];
|
||||
type Number4 = [number, number, number, number];
|
||||
type Rgba = {
|
||||
r: number;
|
||||
g: number;
|
||||
b: number;
|
||||
a?: number;
|
||||
};
|
||||
|
||||
function clearHex(hex: string) {
|
||||
return hex.length % 2 === 0 ? hex : hex.slice(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* HEX > RGB
|
||||
* input: 'xxxxxx' (ex. 'ed15fa') case-insensitive
|
||||
* output: [r, g, b] ([0-255, 0-255, 0-255])
|
||||
*/
|
||||
export function hex2rgb(param: string): [number, number, number] {
|
||||
export function hex2rgb(hex: string): Number3 {
|
||||
const cleanHex = clearHex(hex);
|
||||
|
||||
return [
|
||||
parseInt(param.substring(0, 2), 16),
|
||||
parseInt(param.substring(2, 4), 16),
|
||||
parseInt(param.substring(4, 6), 16),
|
||||
parseInt(cleanHex.substring(0, 2), 16),
|
||||
parseInt(cleanHex.substring(2, 4), 16),
|
||||
parseInt(cleanHex.substring(4, 6), 16),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* RGB > HEX
|
||||
* input: [r, g, b] ([0-255, 0-255, 0-255])
|
||||
* output: 'xxxxxx' (ex. 'ff0000')
|
||||
*/
|
||||
export function rgb2hex(param: [number, number, number]) {
|
||||
const p0 = param[0].toString(16);
|
||||
const p1 = param[1].toString(16);
|
||||
const p2 = param[2].toString(16);
|
||||
return (p0.length === 1 ? '0' + p0 : p0) + (p1.length === 1 ? '0' + p1 : p1) + (p2.length === 1 ? '0' + p2 : p2);
|
||||
export function hex2rgba(hex: string): Number4 {
|
||||
const cleanHex = clearHex(hex);
|
||||
|
||||
return [
|
||||
...hex2rgb(cleanHex),
|
||||
cleanHex.length === 8 ? parseInt(cleanHex.substring(6, 8), 16) : 255,
|
||||
];
|
||||
}
|
||||
|
||||
export function hex2rgbaObj(hex: string): Rgba {
|
||||
const [r, g, b, a] = hex2rgba(hex);
|
||||
|
||||
return { r, g, b, a };
|
||||
}
|
||||
|
||||
export function rgb2hex([r, g, b]: Number3) {
|
||||
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
||||
}
|
||||
|
||||
export function rgba2hex([r, g, b, a]: Number4) {
|
||||
return `${rgb2hex([r, g, b])}${a.toString(16)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -40,16 +65,16 @@ export function rgb2hex(param: [number, number, number]) {
|
||||
* @param Number b The blue color value
|
||||
* @return Array The HSV representation
|
||||
*/
|
||||
export function rgb2hsb([r, g, b]: [number, number, number]): [number, number, number] {
|
||||
export function rgb2hsv([r, g, b]: Number3): Number3 {
|
||||
r /= 255;
|
||||
g /= 255;
|
||||
b /= 255;
|
||||
|
||||
let max = Math.max(r, g, b), min = Math.min(r, g, b);
|
||||
let h!: number, s: number, v: number = max;
|
||||
|
||||
let d = max - min;
|
||||
s = max === 0 ? 0 : d / max;
|
||||
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
||||
const d = max - min;
|
||||
let h!: number;
|
||||
const s: number = max === 0 ? 0 : d / max;
|
||||
const v: number = max;
|
||||
|
||||
if (max === min) {
|
||||
h = 0; // achromatic
|
||||
@ -72,6 +97,10 @@ export function rgb2hsb([r, g, b]: [number, number, number]): [number, number, n
|
||||
return [h, s, v];
|
||||
}
|
||||
|
||||
export function rgba2hsva([r, g, b, a]: Number4): Number4 {
|
||||
return [...rgb2hsv([r, g, b]), a];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an HSV color value to RGB. Conversion formula
|
||||
* adapted from http://en.wikipedia.org/wiki/HSV_color_space.
|
||||
@ -83,14 +112,14 @@ export function rgb2hsb([r, g, b]: [number, number, number]): [number, number, n
|
||||
* @param Number v The value
|
||||
* @return Array The RGB representation
|
||||
*/
|
||||
export function hsb2rgb([h, s, v]: [number, number, number]): [number, number, number] {
|
||||
export function hsv2rgb([h, s, v]: Number3): Number3 {
|
||||
let r!: number, g!: number, b!: number;
|
||||
|
||||
let i = Math.floor(h * 6);
|
||||
let f = h * 6 - i;
|
||||
let p = v * (1 - s);
|
||||
let q = v * (1 - f * s);
|
||||
let t = v * (1 - (1 - f) * s);
|
||||
const i = Math.floor(h * 6);
|
||||
const f = h * 6 - i;
|
||||
const p = v * (1 - s);
|
||||
const q = v * (1 - f * s);
|
||||
const t = v * (1 - (1 - f) * s);
|
||||
|
||||
switch (i % 6) {
|
||||
case 0:
|
||||
@ -132,27 +161,28 @@ export function hsb2rgb([h, s, v]: [number, number, number]): [number, number, n
|
||||
];
|
||||
}
|
||||
|
||||
export async function getAverageColor(url: string): Promise<[number, number, number]> {
|
||||
export function hsva2rgba([h, s, v, a]: Number4): Number4 {
|
||||
return [...hsv2rgb([h, s, v]), a];
|
||||
}
|
||||
|
||||
export async function getAverageColor(url: string): Promise<Number3> {
|
||||
// Only visit every 5 pixels
|
||||
const blockSize = 5;
|
||||
const defaultRGB: [number, number, number] = [0, 0, 0];
|
||||
const black: Number3 = [0, 0, 0];
|
||||
let data;
|
||||
let width;
|
||||
let height;
|
||||
let i = -4;
|
||||
let length;
|
||||
let rgb: [number, number, number] = [0, 0, 0];
|
||||
const rgb: Number3 = [0, 0, 0];
|
||||
let count = 0;
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const context = canvas.getContext && canvas.getContext('2d');
|
||||
if (!context) {
|
||||
return defaultRGB;
|
||||
return black;
|
||||
}
|
||||
|
||||
const image = await preloadImage(url);
|
||||
height = image.naturalHeight || image.offsetHeight || image.height;
|
||||
width = image.naturalWidth || image.offsetWidth || image.width;
|
||||
const height = image.naturalHeight || image.offsetHeight || image.height;
|
||||
const width = image.naturalWidth || image.offsetWidth || image.width;
|
||||
canvas.height = height;
|
||||
canvas.width = width;
|
||||
|
||||
@ -161,10 +191,10 @@ export async function getAverageColor(url: string): Promise<[number, number, num
|
||||
try {
|
||||
data = context.getImageData(0, 0, width, height);
|
||||
} catch (e) {
|
||||
return defaultRGB;
|
||||
return black;
|
||||
}
|
||||
|
||||
length = data.data.length;
|
||||
const length = data.data.length;
|
||||
|
||||
while ((i += blockSize * 4) < length) {
|
||||
if (data.data[i + 3] === 0) continue; // Ignore fully transparent pixels
|
||||
@ -181,50 +211,64 @@ export async function getAverageColor(url: string): Promise<[number, number, num
|
||||
return rgb;
|
||||
}
|
||||
|
||||
export function getColorLuma(rgbColor: [number, number, number]) {
|
||||
const [r, g, b] = rgbColor;
|
||||
const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||
return luma;
|
||||
export function getColorLuma([r, g, b]: Number3) {
|
||||
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/64090995
|
||||
export function hsl2rgb([h, s, l]: [number, number, number]): [number, number, number] {
|
||||
let a = s * Math.min(l, 1 - l);
|
||||
let f = (n: number, k = (n + h / 30) % 12) => l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
||||
export function hsl2rgb([h, s, l]: Number3): Number3 {
|
||||
const a = s * Math.min(l, 1 - l);
|
||||
const f = (n: number, k = (n + h / 30) % 12) => l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
||||
|
||||
return [f(0), f(8), f(4)];
|
||||
}
|
||||
|
||||
// Function was adapted from https://github.com/telegramdesktop/tdesktop/blob/35ff621b5b52f7e3553fb0f990ea13ade7101b8e/Telegram/SourceFiles/data/data_wall_paper.cpp#L518
|
||||
export function getPatternColor(rgbColor: [number, number, number]) {
|
||||
let [hue, saturation, value] = rgb2hsb(rgbColor);
|
||||
export function getPatternColor(rgb: Number3) {
|
||||
const hsv = rgb2hsv(rgb);
|
||||
const [h] = hsv;
|
||||
let [, s, v] = hsv;
|
||||
|
||||
saturation = Math.min(1, saturation + 0.05 + 0.1 * (1 - saturation));
|
||||
value = value > 0.5
|
||||
? Math.max(0, value * 0.65)
|
||||
: Math.max(0, Math.min(1, 1 - value * 0.65));
|
||||
s = Math.min(1, s + 0.05 + 0.1 * (1 - s));
|
||||
v = v > 0.5
|
||||
? Math.max(0, v * 0.65)
|
||||
: Math.max(0, Math.min(1, 1 - v * 0.65));
|
||||
|
||||
const rgb = hsl2rgb([hue * 360, saturation, value]);
|
||||
const hex = rgb2hex(rgb.map((c) => Math.floor(c * 255)) as [number, number, number]);
|
||||
return `#${hex}66`;
|
||||
const newRgb = hsl2rgb([h * 360, s, v]);
|
||||
const mappedRgb = newRgb.map((c) => Math.floor(c * 255)) as Number3;
|
||||
|
||||
return rgba2hex([...mappedRgb, 102]);
|
||||
}
|
||||
|
||||
export const convertToRGBA = (color: number): string => {
|
||||
export function int2cssRgba(color: number): string {
|
||||
const alpha = (color >> 24) & 0xff;
|
||||
const red = (color >> 16) & 0xff;
|
||||
const green = (color >> 8) & 0xff;
|
||||
const blue = color & 0xff;
|
||||
|
||||
const alphaFloat = alpha / 255;
|
||||
|
||||
return `rgba(${red}, ${green}, ${blue}, ${alphaFloat})`;
|
||||
};
|
||||
}
|
||||
|
||||
export const numberToHexColor = (color: number): string => {
|
||||
export function int2hex(color: number): string {
|
||||
return `#${color.toString(16).padStart(6, '0')}`;
|
||||
};
|
||||
}
|
||||
|
||||
export const getTextColor = (color: number): string => {
|
||||
export function getTextColor(color: number): string {
|
||||
const r = (color >> 16) & 0xff;
|
||||
const g = (color >> 8) & 0xff;
|
||||
const b = color & 0xff;
|
||||
const luma = getColorLuma([r, g, b]);
|
||||
return luma > LUMA_THRESHOLD ? 'black' : 'white';
|
||||
};
|
||||
}
|
||||
|
||||
export function lerpRgbaObj(start: Rgba, end: Rgba, interpolationRatio: number): Rgba {
|
||||
const r = Math.round(lerp(start.r, end.r, interpolationRatio));
|
||||
const g = Math.round(lerp(start.g, end.g, interpolationRatio));
|
||||
const b = Math.round(lerp(start.b, end.b, interpolationRatio));
|
||||
const a = start.a !== undefined
|
||||
? Math.round(lerp(start.a, end.a!, interpolationRatio))
|
||||
: undefined;
|
||||
|
||||
return { r, g, b, a };
|
||||
}
|
||||
|
||||
@ -3,19 +3,11 @@ import type { ThemeKey } from '../types';
|
||||
import { requestMutation } from '../lib/fasterdom/fasterdom';
|
||||
import themeColors from '../styles/themes.json';
|
||||
import { animate } from './animation';
|
||||
import { lerp } from './math';
|
||||
|
||||
type RGBAColor = {
|
||||
r: number;
|
||||
g: number;
|
||||
b: number;
|
||||
a?: number;
|
||||
};
|
||||
import { hex2rgbaObj, lerpRgbaObj } from './colors.ts';
|
||||
|
||||
let isInitialized = false;
|
||||
|
||||
const DECIMAL_PLACES = 3;
|
||||
const HEX_COLOR_REGEX = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?$/i;
|
||||
const DURATION_MS = 200;
|
||||
const ENABLE_ANIMATION_DELAY_MS = 500;
|
||||
const RGB_VARIABLES = new Set([
|
||||
@ -34,7 +26,7 @@ const DISABLE_ANIMATION_CSS = `
|
||||
|
||||
const colors = (Object.keys(themeColors) as Array<keyof typeof themeColors>).map((property) => ({
|
||||
property,
|
||||
colors: [hexToRgb(themeColors[property][0]), hexToRgb(themeColors[property][1])],
|
||||
colors: [hex2rgbaObj(themeColors[property][0]), hex2rgbaObj(themeColors[property][1])],
|
||||
}));
|
||||
|
||||
const injectCss = (css: string) => {
|
||||
@ -97,35 +89,11 @@ function transition(t: number) {
|
||||
return 1 - ((1 - t) ** 3.5);
|
||||
}
|
||||
|
||||
export function hexToRgb(hex: string): RGBAColor {
|
||||
const result = HEX_COLOR_REGEX.exec(hex)!;
|
||||
|
||||
return {
|
||||
r: parseInt(result[1], 16),
|
||||
g: parseInt(result[2], 16),
|
||||
b: parseInt(result[3], 16),
|
||||
a: result[4] !== undefined ? parseInt(result[4], 16) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function lerpRgb(start: RGBAColor, end: RGBAColor, interpolationRatio: number): RGBAColor {
|
||||
const r = Math.round(lerp(start.r, end.r, interpolationRatio));
|
||||
const g = Math.round(lerp(start.g, end.g, interpolationRatio));
|
||||
const b = Math.round(lerp(start.b, end.b, interpolationRatio));
|
||||
const a = start.a !== undefined
|
||||
? Math.round(lerp(start.a, end.a!, interpolationRatio))
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
r, g, b, a,
|
||||
};
|
||||
}
|
||||
|
||||
function applyColorAnimationStep(startIndex: number, endIndex: number, interpolationRatio: number = 1) {
|
||||
colors.forEach(({ property, colors: propertyColors }) => {
|
||||
const {
|
||||
r, g, b, a,
|
||||
} = lerpRgb(propertyColors[startIndex], propertyColors[endIndex], interpolationRatio);
|
||||
} = lerpRgbaObj(propertyColors[startIndex], propertyColors[endIndex], interpolationRatio);
|
||||
|
||||
const roundedA = a !== undefined ? Math.round((a / 255) * 10 ** DECIMAL_PLACES) / 10 ** DECIMAL_PLACES : undefined;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user