From 6e11924c27288e590de8f5b675fb53704581bcc8 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sat, 28 Dec 2019 18:49:35 +0200 Subject: [PATCH] underlay customization, updated contrast calculations to account for alpha blending --- src/App.scss | 1 + .../style_switcher/style_switcher.js | 47 +++++++++++-------- .../style_switcher/style_switcher.vue | 14 ++++++ src/services/color_convert/color_convert.js | 23 +++++++++ src/services/style_setter/style_setter.js | 37 ++++++++++----- 5 files changed, 91 insertions(+), 31 deletions(-) diff --git a/src/App.scss b/src/App.scss index 3b03a761..b6d4943a 100644 --- a/src/App.scss +++ b/src/App.scss @@ -32,6 +32,7 @@ h4 { min-height: 100vh; max-width: 980px; background-color: rgba(0,0,0,0.15); + background-color: var(--underlay, rgba(0,0,0,0.15)); align-content: flex-start; } diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js index b450dc8e..602a635e 100644 --- a/src/components/style_switcher/style_switcher.js +++ b/src/components/style_switcher/style_switcher.js @@ -1,4 +1,4 @@ -import { rgb2hex, hex2rgb, getContrastRatio, alphaBlend } from '../../services/color_convert/color_convert.js' +import { rgb2hex, hex2rgb, getContrastRatio, getContrastRatioLayers, alphaBlend } from '../../services/color_convert/color_convert.js' import { set, delete as del } from 'vue' import { merge } from 'lodash' import { generateCompat, generateColors, generateShadows, generateRadii, generateFonts, composePreset, getThemes } from '../../services/style_setter/style_setter.js' @@ -53,6 +53,9 @@ export default { bgColorLocal: '', bgOpacityLocal: undefined, + underlayColorLocal: '', + underlayOpacityLocal: undefined, + fgColorLocal: '', fgTextColorLocal: undefined, fgLinkColorLocal: undefined, @@ -145,6 +148,8 @@ export default { accent: this.accentColorLocal, + underlay: this.underlayColorLocal, + panel: this.panelColorLocal, panelText: this.panelTextColorLocal, panelLink: this.panelLinkColorLocal, @@ -182,7 +187,8 @@ export default { panel: this.panelOpacityLocal, topBar: this.topBarOpacityLocal, border: this.borderOpacityLocal, - faint: this.faintOpacityLocal + faint: this.faintOpacityLocal, + underlay: this.underlayOpacityLocal } }, currentRadii () { @@ -240,6 +246,7 @@ export default { const bgs = { bg: hex2rgb(colors.bg), + underlay: hex2rgb(colors.underlay), btn: hex2rgb(colors.btn), panel: hex2rgb(colors.panel), topBar: hex2rgb(colors.topBar), @@ -249,29 +256,31 @@ export default { badgeNotification: hex2rgb(colors.badgeNotification) } - /* This is a bit confusing because "bottom layer" used is text color - * This is done to get worst case scenario when background below transparent - * layer matches text color, making it harder to read the lower alpha is. - */ - const ratios = { - bgText: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.text), fgs.text), - bgLink: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.link), fgs.link), - bgRed: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.red), fgs.red), - bgGreen: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.green), fgs.green), - bgBlue: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.blue), fgs.blue), - bgOrange: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.orange), fgs.orange), + const bg = [bgs.bg, opacity.bg] + const underlay = [bgs.underlay, opacity.underlay] + const panel = [underlay, bg] + + const ratios = { + bgText: getContrastRatioLayers(fgs.text, panel, fgs.text), + bgLink: getContrastRatioLayers(fgs.link, panel, fgs.link), + bgRed: getContrastRatioLayers(fgs.red, panel, fgs.red), + bgGreen: getContrastRatioLayers(fgs.green, panel, fgs.green), + bgBlue: getContrastRatioLayers(fgs.blue, panel, fgs.blue), + bgOrange: getContrastRatioLayers(fgs.orange, panel, fgs.orange), + + // TODO what's this? tintText: getContrastRatio(alphaBlend(bgs.bg, 0.5, fgs.panelText), fgs.text), - panelText: getContrastRatio(alphaBlend(bgs.panel, opacity.panel, fgs.panelText), fgs.panelText), - panelLink: getContrastRatio(alphaBlend(bgs.panel, opacity.panel, fgs.panelLink), fgs.panelLink), + panelText: getContrastRatioLayers(fgs.text, [...panel, [bgs.panel, opacity.panel]], fgs.panelText), + panelLink: getContrastRatioLayers(fgs.link, [...panel, [bgs.panel, opacity.panel]], fgs.panelLink), - btnText: getContrastRatio(alphaBlend(bgs.btn, opacity.btn, fgs.btnText), fgs.btnText), + btnText: getContrastRatioLayers(fgs.text, [...panel, [bgs.btn, opacity.btn]], fgs.btnText), - inputText: getContrastRatio(alphaBlend(bgs.input, opacity.input, fgs.inputText), fgs.inputText), + inputText: getContrastRatioLayers(fgs.text, [...panel, [bgs.input, opacity.input]], fgs.inputText), - topBarText: getContrastRatio(alphaBlend(bgs.topBar, opacity.topBar, fgs.topBarText), fgs.topBarText), - topBarLink: getContrastRatio(alphaBlend(bgs.topBar, opacity.topBar, fgs.topBarLink), fgs.topBarLink) + topBarText: getContrastRatioLayers(fgs.text, [...panel, [bgs.topBar, opacity.topBar]], fgs.topBarText), + topBarLink: getContrastRatioLayers(fgs.link, [...panel, [bgs.topBar, opacity.topBar]], fgs.topBarLink) } return Object.entries(ratios).reduce((acc, [k, v]) => { acc[k] = hints(v); return acc }, {}) diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue index 2ecd275a..38ca2017 100644 --- a/src/components/style_switcher/style_switcher.vue +++ b/src/components/style_switcher/style_switcher.vue @@ -366,6 +366,20 @@ :fallback="previewTheme.opacity.faint || 0.5" /> +
+

{{ $t('settings.style.advanced_colors.underlay') }}

+ + +
console.log('%c##########', 'background: ' + color + '; color: ' + color) + const rgb2hex = (r, g, b) => { if (r === null || typeof r === 'undefined') { return undefined @@ -78,6 +81,16 @@ const getContrastRatio = (a, b) => { return (l1 + 0.05) / (l2 + 0.05) } +/** + * Same as `getContrastRatio` but for multiple layers in-between + * + * @param {Object} text - text color (topmost layer) + * @param {[Object, Number]} layers[] - layers between text and bedrock + * @param {Object} bedrock - layer at the very bottom + */ +export const getContrastRatioLayers = (text, layers, bedrock) => { + return getContrastRatio(alphaBlendLayers(bedrock, layers), text) +} /** * This performs alpha blending between solid background and semi-transparent foreground @@ -97,6 +110,16 @@ const alphaBlend = (fg, fga, bg) => { }, {}) } +/** + * Same as `alphaBlend` but for multiple layers in-between + * + * @param {Object} bedrock - layer at the very bottom + * @param {[Object, Number]} layers[] - layers between text and bedrock + */ +export const alphaBlendLayers = (bedrock, layers) => layers.reduce((acc, [color, opacity]) => { + return alphaBlend(color, opacity, acc) +}, bedrock) + const invert = (rgb) => { return 'rgb'.split('').reduce((acc, c) => { acc[c] = 255 - rgb[c] diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js index 19a06587..8740fc55 100644 --- a/src/services/style_setter/style_setter.js +++ b/src/services/style_setter/style_setter.js @@ -1,6 +1,6 @@ import { times } from 'lodash' import { brightness, invertLightness, convert, contrastRatio } from 'chromatism' -import { rgb2hex, hex2rgb, mixrgb, getContrastRatio, alphaBlend } from '../color_convert/color_convert.js' +import { rgb2hex, hex2rgb, mixrgb, getContrastRatio, alphaBlend, alphaBlendLayers } from '../color_convert/color_convert.js' // While this is not used anymore right now, I left it in if we want to do custom // styles that aren't just colors, so user can pick from a few different distinct @@ -64,8 +64,10 @@ const getTextColor = function (bg, text, preserve) { const base = typeof text.a !== 'undefined' ? { a: text.a } : {} const result = Object.assign(base, invertLightness(text).rgb) if (!preserve && getContrastRatio(bg, result) < 4.5) { + // B&W return contrastRatio(bg, text).rgb } + // Inverted color return result } return text @@ -173,7 +175,8 @@ const generateColors = (input) => { const opacity = Object.assign({ alert: 0.5, input: 0.5, - faint: 0.5 + faint: 0.5, + underlay: 0.15 }, Object.entries(input.opacity || {}).reduce((acc, [k, v]) => { if (typeof v !== 'undefined') { acc[k] = v @@ -210,28 +213,37 @@ const generateColors = (input) => { colors.faint = col.faint || Object.assign({}, col.text) colors.bg = col.bg - colors.lightBg = col.lightBg || brightness(5, colors.bg).rgb + colors.lightBg = col.lightBg || brightness(5 * mod, colors.bg).rgb + + const underlay = [col.underlay, opacity.underlay] + const fg = [col.fg, opacity.fg] + const bg = [col.bg, opacity.bg] colors.fg = col.fg - colors.fgText = col.fgText || getTextColor(colors.fg, colors.text) - colors.fgLink = col.fgLink || getTextColor(colors.fg, colors.link, true) + colors.fgText = col.fgText || getTextColor(alphaBlendLayers(colors.text, [underlay, bg, fg]), colors.text) + colors.fgLink = col.fgLink || getTextColor(alphaBlendLayers(colors.link, [underlay, bg, fg]), colors.link, true) + colors.underlay = col.underlay || hex2rgb('#000000') colors.border = col.border || brightness(2 * mod, colors.fg).rgb colors.btn = col.btn || Object.assign({}, col.fg) - colors.btnText = col.btnText || getTextColor(colors.btn, colors.fgText) + const btn = [colors.btn, opacity.btn || 1] + colors.btnText = col.btnText || getTextColor(alphaBlendLayers(colors.fgText, [underlay, bg, fg, btn]), colors.fgText) colors.input = col.input || Object.assign({}, col.fg) - colors.inputText = col.inputText || getTextColor(colors.input, colors.lightText) + const inputCol = [colors.input, opacity.input] + colors.inputText = col.inputText || getTextColor(alphaBlendLayers(colors.lightText, [underlay, bg, fg, inputCol]), colors.lightText) colors.panel = col.panel || Object.assign({}, col.fg) - colors.panelText = col.panelText || getTextColor(colors.panel, colors.fgText) - colors.panelLink = col.panelLink || getTextColor(colors.panel, colors.fgLink) - colors.panelFaint = col.panelFaint || getTextColor(colors.panel, colors.faint) + const panel = [colors.panel, opacity.panel] + colors.panelText = col.panelText || getTextColor(alphaBlendLayers(colors.fgText, [underlay, bg, panel]), colors.fgText) + colors.panelLink = col.panelLink || getTextColor(alphaBlendLayers(colors.fgLink, [underlay, bg, panel]), colors.fgLink) + colors.panelFaint = col.panelFaint || getTextColor(alphaBlendLayers(colors.faint, [underlay, bg, panel]), colors.faint) colors.topBar = col.topBar || Object.assign({}, col.fg) - colors.topBarText = col.topBarText || getTextColor(colors.topBar, colors.fgText) - colors.topBarLink = col.topBarLink || getTextColor(colors.topBar, colors.fgLink) + const topBar = [colors.topBar, opacity.topBar] + colors.topBarText = col.topBarText || getTextColor(alphaBlendLayers(colors.fgText, [topBar]), colors.fgText) + colors.topBarLink = col.topBarLink || getTextColor(alphaBlendLayers(colors.fgLink, [topBar]), colors.fgLink) colors.faintLink = col.faintLink || Object.assign({}, col.link || col.accent) colors.linkBg = alphaBlend(colors.link, 0.4, colors.bg) @@ -255,6 +267,7 @@ const generateColors = (input) => { colors.badgeNotificationText = contrastRatio(colors.badgeNotification).rgb Object.entries(opacity).forEach(([ k, v ]) => { + console.log(k) if (typeof v === 'undefined') return if (k === 'alert') { colors.alertError.a = v