From 1607c728bca19a003ca08b64b4d2afc73e4d1e2a Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 9 Mar 2022 14:00:02 -0300 Subject: first banner implementation with mui --- .../src/mui/colors/manipulation.ts | 273 +++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 packages/taler-wallet-webextension/src/mui/colors/manipulation.ts (limited to 'packages/taler-wallet-webextension/src/mui/colors/manipulation.ts') diff --git a/packages/taler-wallet-webextension/src/mui/colors/manipulation.ts b/packages/taler-wallet-webextension/src/mui/colors/manipulation.ts new file mode 100644 index 000000000..633c80c94 --- /dev/null +++ b/packages/taler-wallet-webextension/src/mui/colors/manipulation.ts @@ -0,0 +1,273 @@ + +export type ColorFormat = ColorFormatWithAlpha | ColorFormatWithoutAlpha +export type ColorFormatWithAlpha = 'rgb' | 'hsl'; +export type ColorFormatWithoutAlpha = 'rgba' | 'hsla'; +export type ColorObject = ColorObjectWithAlpha | ColorObjectWithoutAlpha +export interface ColorObjectWithAlpha { + type: ColorFormatWithAlpha; + values: [number, number, number]; + colorSpace?: 'srgb' | 'display-p3' | 'a98-rgb' | 'prophoto-rgb' | 'rec-2020'; +} +export interface ColorObjectWithoutAlpha { + type: ColorFormatWithoutAlpha; + values: [number, number, number, number]; + colorSpace?: 'srgb' | 'display-p3' | 'a98-rgb' | 'prophoto-rgb' | 'rec-2020'; +} + + +/** + * Returns a number whose value is limited to the given range. + * @param {number} value The value to be clamped + * @param {number} min The lower boundary of the output range + * @param {number} max The upper boundary of the output range + * @returns {number} A number in the range [min, max] + */ +function clamp(value: number, min: number = 0, max: number = 1): number { + // if (process.env.NODE_ENV !== 'production') { + // if (value < min || value > max) { + // console.error(`MUI: The value provided ${value} is out of range [${min}, ${max}].`); + // } + // } + + return Math.min(Math.max(min, value), max); +} + +/** + * Converts a color from CSS hex format to CSS rgb format. + * @param {string} color - Hex color, i.e. #nnn or #nnnnnn + * @returns {string} A CSS rgb color string + */ +export function hexToRgb(color: string): string { + color = color.substr(1); + + const re = new RegExp(`.{1,${color.length >= 6 ? 2 : 1}}`, 'g'); + let colors = color.match(re); + + if (colors && colors[0].length === 1) { + colors = colors.map((n) => n + n); + } + + return colors + ? `rgb${colors.length === 4 ? 'a' : ''}(${colors + .map((n, index) => { + return index < 3 ? parseInt(n, 16) : Math.round((parseInt(n, 16) / 255) * 1000) / 1000; + }) + .join(', ')})` + : ''; +} + +function intToHex(int: number): string { + const hex = int.toString(16); + return hex.length === 1 ? `0${hex}` : hex; +} + +/** + * Returns an object with the type and values of a color. + * + * Note: Does not support rgb % values. + * @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla() + * @returns {object} - A MUI color object: {type: string, values: number[]} + */ +export function decomposeColor(color: string): ColorObject { + const colorSpace = undefined; + if (color.charAt(0) === '#') { + return decomposeColor(hexToRgb(color)); + } + + const marker = color.indexOf('('); + const type = color.substring(0, marker); + if (type != 'rgba' && type != 'hsla' && type != 'rgb' && type != 'hsl') { + } + + const values = color.substring(marker + 1, color.length - 1).split(',') + if (type == 'rgb' || type == 'hsl') { + return { type, colorSpace, values: [parseFloat(values[0]), parseFloat(values[1]), parseFloat(values[2])] } + } + if (type == 'rgba' || type == 'hsla') { + return { type, colorSpace, values: [parseFloat(values[0]), parseFloat(values[1]), parseFloat(values[2]), parseFloat(values[3])] } + } + throw new Error(`Unsupported '${color}' color. The following formats are supported: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()`) +} + +/** + * Converts a color object with type and values to a string. + * @param {object} color - Decomposed color + * @param {string} color.type - One of: 'rgb', 'rgba', 'hsl', 'hsla' + * @param {array} color.values - [n,n,n] or [n,n,n,n] + * @returns {string} A CSS color string + */ +export function recomposeColor(color: ColorObject): string { + const { type, values: valuesNum } = color; + + const valuesStr: string[] = []; + if (type.indexOf('rgb') !== -1) { + // Only convert the first 3 values to int (i.e. not alpha) + valuesNum.map((n, i) => (i < 3 ? parseInt(String(n), 10) : n)).forEach((n, i) => valuesStr[i] = String(n)); + } else if (type.indexOf('hsl') !== -1) { + valuesStr[0] = String(valuesNum[0]) + valuesStr[1] = `${valuesNum[1]}%`; + valuesStr[2] = `${valuesNum[2]}%`; + if (type === 'hsla') { + valuesStr[3] = String(valuesNum[3]) + } + } + + return `${type}(${valuesStr.join(', ')})`; +} + +/** + * Converts a color from CSS rgb format to CSS hex format. + * @param {string} color - RGB color, i.e. rgb(n, n, n) + * @returns {string} A CSS rgb color string, i.e. #nnnnnn + */ +export function rgbToHex(color: string): string { + // Idempotent + if (color.indexOf('#') === 0) { + return color; + } + + const { values } = decomposeColor(color); + return `#${values.map((n, i) => intToHex(i === 3 ? Math.round(255 * n) : n)).join('')}`; +} + +/** + * Converts a color from hsl format to rgb format. + * @param {string} color - HSL color values + * @returns {string} rgb color values + */ +export function hslToRgb(color: string): string { + const colorObj = decomposeColor(color); + const { values } = colorObj; + const h = values[0]; + const s = values[1] / 100; + const l = values[2] / 100; + 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); + + if (colorObj.type === 'hsla') { + return recomposeColor({ + type: 'rgba', values: [ + Math.round(f(0) * 255), + Math.round(f(8) * 255), + Math.round(f(4) * 255), + colorObj.values[3] + ] + }) + } + + return recomposeColor({ + type: 'rgb', values: [ + Math.round(f(0) * 255), + Math.round(f(8) * 255), + Math.round(f(4) * 255)] + }); +} +/** + * The relative brightness of any point in a color space, + * normalized to 0 for darkest black and 1 for lightest white. + * + * Formula: https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests + * @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color() + * @returns {number} The relative brightness of the color in the range 0 - 1 + */ +export function getLuminance(color: string): number { + const colorObj = decomposeColor(color); + + const rgb2 = colorObj.type === 'hsl' ? decomposeColor(hslToRgb(color)).values : colorObj.values; + const rgb = rgb2.map((val) => { + val /= 255; // normalized + return val <= 0.03928 ? val / 12.92 : ((val + 0.055) / 1.055) ** 2.4; + }) as typeof rgb2; + + // Truncate at 3 digits + return Number((0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2]).toFixed(3)); +} + +/** + * Calculates the contrast ratio between two colors. + * + * Formula: https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests + * @param {string} foreground - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla() + * @param {string} background - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla() + * @returns {number} A contrast ratio value in the range 0 - 21. + */ +export function getContrastRatio(foreground: string, background: string): number { + const lumA = getLuminance(foreground); + const lumB = getLuminance(background); + return (Math.max(lumA, lumB) + 0.05) / (Math.min(lumA, lumB) + 0.05); +} + +/** + * Sets the absolute transparency of a color. + * Any existing alpha values are overwritten. + * @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color() + * @param {number} value - value to set the alpha channel to in the range 0 - 1 + * @returns {string} A CSS color string. Hex input values are returned as rgb + */ +export function alpha(color: string, value: number): string { + const colorObj = decomposeColor(color); + value = clamp(value); + + if (colorObj.type === 'rgb' || colorObj.type === 'hsl') { + colorObj.type += 'a'; + } + colorObj.values[3] = value; + + return recomposeColor(colorObj); +} + +/** + * Darkens a color. + * @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color() + * @param {number} coefficient - multiplier in the range 0 - 1 + * @returns {string} A CSS color string. Hex input values are returned as rgb + */ +export function darken(color: string, coefficient: number): string { + const colorObj = decomposeColor(color); + coefficient = clamp(coefficient); + + if (colorObj.type.indexOf('hsl') !== -1) { + colorObj.values[2] *= 1 - coefficient; + } else if (colorObj.type.indexOf('rgb') !== -1 || colorObj.type.indexOf('color') !== -1) { + for (let i = 0; i < 3; i += 1) { + colorObj.values[i] *= 1 - coefficient; + } + } + return recomposeColor(colorObj); +} + +/** + * Lightens a color. + * @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color() + * @param {number} coefficient - multiplier in the range 0 - 1 + * @returns {string} A CSS color string. Hex input values are returned as rgb + */ +export function lighten(color: string, coefficient: number): string { + const colorObj = decomposeColor(color); + coefficient = clamp(coefficient); + + if (colorObj.type.indexOf('hsl') !== -1) { + colorObj.values[2] += (100 - colorObj.values[2]) * coefficient; + } else if (colorObj.type.indexOf('rgb') !== -1) { + for (let i = 0; i < 3; i += 1) { + colorObj.values[i] += (255 - colorObj.values[i]) * coefficient; + } + } else if (colorObj.type.indexOf('color') !== -1) { + for (let i = 0; i < 3; i += 1) { + colorObj.values[i] += (1 - colorObj.values[i]) * coefficient; + } + } + + return recomposeColor(colorObj); +} + +/** + * Darken or lighten a color, depending on its luminance. + * Light colors are darkened, dark colors are lightened. + * @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color() + * @param {number} coefficient=0.15 - multiplier in the range 0 - 1 + * @returns {string} A CSS color string. Hex input values are returned as rgb + */ +export function emphasize(color: string, coefficient: number = 0.15): string { + return getLuminance(color) > 0.5 ? darken(color, coefficient) : lighten(color, coefficient); +} -- cgit v1.2.3