diff --git a/src/lib/components/colors/Header.svelte b/src/lib/components/colors/Header.svelte index 7e339c1b..04882063 100644 --- a/src/lib/components/colors/Header.svelte +++ b/src/lib/components/colors/Header.svelte @@ -9,12 +9,16 @@ export let type: 'bg' | 'fg'; export let color: Writable; export let format: ColorFormatId; + export let premultipliedFg: PlainColorObject | null; $: targetSpace = getSpaceFromFormatId(format); $: display = serialize($color, { inGamut: false, format }); $: displayType = type === 'bg' ? 'Background' : 'Foreground'; $: editing = false; $: inputValue = ''; + $: displayPremultipliedFg = premultipliedFg + ? serialize(premultipliedFg, { inGamut: false, format }) + : ''; let hasError = false; // When not editing, sync input value with color (e.g. when sliders change) @@ -89,6 +93,15 @@ {#if hasError}
Could not parse input as a valid color.
{/if} + {#if premultipliedFg} +
+ {displayPremultipliedFg} + +
+ {/if} diff --git a/src/lib/stores.ts b/src/lib/stores.ts index bdfcdeaa..e44c0a77 100644 --- a/src/lib/stores.ts +++ b/src/lib/stores.ts @@ -10,12 +10,14 @@ import { sRGB, } from 'colorjs.io/fn'; import type { PlainColorObject } from 'colorjs.io/types/src/color'; -import { writable } from 'svelte/store'; +import { derived, writable } from 'svelte/store'; // eslint-disable-next-line import/no-unresolved import { browser, dev } from '$app/environment'; import type { ColorFormatId } from '$lib/constants'; +import { premultiplyFG } from './utils'; + // Register supported color spaces ColorSpace.register(HSL); ColorSpace.register(Lab); @@ -51,6 +53,7 @@ const INITIAL_FG = { export const format = writable(INITIAL_VALUES.format); export const bg = writable(INITIAL_BG); export const fg = writable(INITIAL_FG); +export const premultipliedFg = derived([fg, bg, format], premultiplyFG); export const reset = () => { bg.set(INITIAL_BG); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index e973e464..471f640f 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,4 +1,4 @@ -import { clone, display, serialize, set, steps, to } from 'colorjs.io/fn'; +import { clone, display, mix, serialize, set, steps, to } from 'colorjs.io/fn'; import type { PlainColorObject } from 'colorjs.io/types/src/color'; import type { ColorFormatId } from '$lib/constants'; @@ -88,3 +88,22 @@ export const storeValuesToHash = ( const fgParam = encodeColor(fg, format); return encodeURIComponent(`${format}__${bgParam}__${fgParam}`); }; + +export const premultiplyFG = ([fg, bg, format]: [ + fg: PlainColorObject, + bg: PlainColorObject, + format: ColorFormatId, +]) => { + if (fg.alpha === 1) return; + const bgNoAlpha = clone(bg); + bgNoAlpha.alpha = 1; + const fgNoAlpha = clone(fg); + fgNoAlpha.alpha = 1; + + return mix(fgNoAlpha, bgNoAlpha, 1 - fg.alpha, { + // We always mix in srgb, as we are approximating the color as it will be + // displayed on a monitor. + space: 'srgb', + outputSpace: getSpaceFromFormatId(format), + }); +}; diff --git a/test/js/lib/components/colors/Header.spec.ts b/test/js/lib/components/colors/Header.spec.ts index 7849c24f..3dc428e8 100644 --- a/test/js/lib/components/colors/Header.spec.ts +++ b/test/js/lib/components/colors/Header.spec.ts @@ -11,6 +11,7 @@ describe('Header', () => { const { getByLabelText } = render(Header, { type: 'bg', color, + premultipliedFg: HSL_WHITE, format: 'hsl', }); const input = getByLabelText('Background Color'); @@ -31,6 +32,7 @@ describe('Header', () => { const { getByText, getByLabelText } = render(Header, { type: 'fg', color, + premultipliedFg: HSL_WHITE, format: 'hsl', }); const input = getByLabelText('Foreground Color'); @@ -55,6 +57,7 @@ describe('Header', () => { const { getByText, getByLabelText } = render(Header, { type: 'fg', color, + premultipliedFg: HSL_WHITE, format: 'hsl', }); const input = getByLabelText('Foreground Color'); @@ -78,6 +81,7 @@ describe('Header', () => { const { getByLabelText } = render(Header, { type: 'fg', color, + premultipliedFg: HSL_WHITE, format: 'hsl', }); const input = getByLabelText('Foreground Color');