import { internalAssertColorUnit, internalRoundColorFloat } from "../internal.ts" import type { LinearRgbColorValue, SrgbColorValue } from "../types.ts" import { createLinearRgbColorValue, createSrgbColorValue } from "./construct.ts" import { linearRgbColorValueToSrgbColorValue, srgbColorValueToLinearRgbColorValue, } from "./convert.ts" const internalOpaqueBlackLinearRgbColorValue: LinearRgbColorValue = { red: 0, green: 0, blue: 0, alpha: 1, } const internalOpaqueWhiteLinearRgbColorValue: LinearRgbColorValue = { red: 1, green: 1, blue: 1, alpha: 1, } const internalOpaqueGrayLinearRgbColorValue: LinearRgbColorValue = { red: 0.5, green: 0.5, blue: 0.5, alpha: 1, } const internalMixNumber = (first: number, second: number, weight: number): number => { return internalRoundColorFloat(first * (1 - weight) + second * weight) } const internalCompositeLinearRgbChannels = ( foreground: number, background: number, foregroundAlpha: number, backgroundAlpha: number, outputAlpha: number, ): number => { if (outputAlpha === 0) { return 0 } return internalRoundColorFloat( (foreground * foregroundAlpha + background * backgroundAlpha * (1 - foregroundAlpha)) / outputAlpha, ) } const internalMixSrgbColorValueWithLinearReference = ( color: SrgbColorValue, referenceColor: LinearRgbColorValue, amount: number, ): SrgbColorValue => { internalAssertColorUnit(amount, "amount") const normalizedColor = createSrgbColorValue(color) return linearRgbColorValueToSrgbColorValue( mixLinearRgbColorValues( srgbColorValueToLinearRgbColorValue(normalizedColor), referenceColor, amount, ), ) } /** * @description 在 Linear RGB 层混合两个颜色值。 * * `weight` 表示第二个颜色值的占比。 * * @example * ``` * // Expect: { red: 0.5, green: 0, blue: 0.5, alpha: 1 } * const example1 = mixLinearRgbColorValues( * { red: 1, green: 0, blue: 0, alpha: 1 }, * { red: 0, green: 0, blue: 1, alpha: 1 }, * 0.5, * ) * * // Expect: { red: 0.75, green: 0.75, blue: 0.75, alpha: 1 } * const example2 = mixLinearRgbColorValues( * { red: 1, green: 1, blue: 1, alpha: 1 }, * { red: 0, green: 0, blue: 0, alpha: 1 }, * 0.25, * ) * ``` */ export const mixLinearRgbColorValues = ( first: LinearRgbColorValue, second: LinearRgbColorValue, weight = 0.5, ): LinearRgbColorValue => { internalAssertColorUnit(weight, "weight") const normalizedFirst = createLinearRgbColorValue(first) const normalizedSecond = createLinearRgbColorValue(second) return createLinearRgbColorValue({ red: internalMixNumber(normalizedFirst.red, normalizedSecond.red, weight), green: internalMixNumber(normalizedFirst.green, normalizedSecond.green, weight), blue: internalMixNumber(normalizedFirst.blue, normalizedSecond.blue, weight), alpha: internalMixNumber(normalizedFirst.alpha, normalizedSecond.alpha, weight), }) } /** * @description 在 sRGB 层混合两个颜色值。 * * 该函数会沿合法路径 `sRGB -> Linear RGB -> mix -> sRGB` 计算。 * * @example * ``` * // Expect: { red: 188, green: 0, blue: 188, alpha: 1 } * const example1 = mixSrgbColorValues( * { red: 255, green: 0, blue: 0, alpha: 1 }, * { red: 0, green: 0, blue: 255, alpha: 1 }, * 0.5, * ) * * // Expect: { red: 255, green: 188, blue: 188, alpha: 1 } * const example2 = mixSrgbColorValues( * { red: 255, green: 0, blue: 0, alpha: 1 }, * { red: 255, green: 255, blue: 255, alpha: 1 }, * 0.5, * ) * ``` */ export const mixSrgbColorValues = ( first: SrgbColorValue, second: SrgbColorValue, weight = 0.5, ): SrgbColorValue => { internalAssertColorUnit(weight, "weight") const normalizedFirst = createSrgbColorValue(first) const normalizedSecond = createSrgbColorValue(second) const mixedLinearRgbColorValue = mixLinearRgbColorValues( srgbColorValueToLinearRgbColorValue(normalizedFirst), srgbColorValueToLinearRgbColorValue(normalizedSecond), weight, ) return linearRgbColorValueToSrgbColorValue(mixedLinearRgbColorValue) } /** * @description 将一个 Linear RGB 前景色合成到另一个 Linear RGB 背景色之上。 * * 该函数在支线核心计算层直接执行 alpha 合成。 * * @example * ``` * // Expect: { red: 0.5, green: 0.5, blue: 1, alpha: 1 } * const example1 = compositeLinearRgbColorValueOverBackground( * { red: 1, green: 1, blue: 1, alpha: 0.5 }, * { red: 0, green: 0, blue: 1, alpha: 1 }, * ) * * // Expect: { red: 1, green: 0, blue: 0, alpha: 1 } * const example2 = compositeLinearRgbColorValueOverBackground( * { red: 1, green: 0, blue: 0, alpha: 1 }, * { red: 0, green: 0, blue: 1, alpha: 1 }, * ) * ``` */ export const compositeLinearRgbColorValueOverBackground = ( foreground: LinearRgbColorValue, background: LinearRgbColorValue, ): LinearRgbColorValue => { const normalizedForeground = createLinearRgbColorValue(foreground) const normalizedBackground = createLinearRgbColorValue(background) const outputAlpha = internalRoundColorFloat( normalizedForeground.alpha + normalizedBackground.alpha * (1 - normalizedForeground.alpha), ) return createLinearRgbColorValue({ red: internalCompositeLinearRgbChannels( normalizedForeground.red, normalizedBackground.red, normalizedForeground.alpha, normalizedBackground.alpha, outputAlpha, ), green: internalCompositeLinearRgbChannels( normalizedForeground.green, normalizedBackground.green, normalizedForeground.alpha, normalizedBackground.alpha, outputAlpha, ), blue: internalCompositeLinearRgbChannels( normalizedForeground.blue, normalizedBackground.blue, normalizedForeground.alpha, normalizedBackground.alpha, outputAlpha, ), alpha: outputAlpha, }) } /** * @description 将一个 sRGB 前景色沿合法路径合成到另一个 sRGB 背景色之上。 * * 该函数会沿合法路径 `sRGB -> Linear RGB -> composite -> sRGB` 计算。 * * @example * ``` * // Expect: { red: 188, green: 188, blue: 255, alpha: 1 } * const example1 = compositeSrgbColorValueOverBackground( * { red: 255, green: 255, blue: 255, alpha: 0.5 }, * { red: 0, green: 0, blue: 255, alpha: 1 }, * ) * * // Expect: { red: 255, green: 0, blue: 0, alpha: 1 } * const example2 = compositeSrgbColorValueOverBackground( * { red: 255, green: 0, blue: 0, alpha: 1 }, * { red: 0, green: 0, blue: 255, alpha: 1 }, * ) * ``` */ export const compositeSrgbColorValueOverBackground = ( foreground: SrgbColorValue, background: SrgbColorValue, ): SrgbColorValue => { const normalizedForeground = createSrgbColorValue(foreground) const normalizedBackground = createSrgbColorValue(background) return linearRgbColorValueToSrgbColorValue( compositeLinearRgbColorValueOverBackground( srgbColorValueToLinearRgbColorValue(normalizedForeground), srgbColorValueToLinearRgbColorValue(normalizedBackground), ), ) } /** * @description 调整一个 sRGB 颜色值的透明度。 * * @example * ``` * // Expect: { red: 51, green: 102, blue: 153, alpha: 0.4 } * const example1 = fadeSrgbColorValue({ red: 51, green: 102, blue: 153, alpha: 1 }, 0.4) * * // Expect: { red: 51, green: 102, blue: 153, alpha: 0 } * const example2 = fadeSrgbColorValue({ red: 51, green: 102, blue: 153, alpha: 1 }, 0) * ``` */ export const fadeSrgbColorValue = (color: SrgbColorValue, alpha: number): SrgbColorValue => { internalAssertColorUnit(alpha, "alpha") const normalizedColor = createSrgbColorValue(color) return createSrgbColorValue({ red: normalizedColor.red, green: normalizedColor.green, blue: normalizedColor.blue, alpha, }) } /** * @description 沿合法路径将一个 sRGB 颜色值向白色方向推进。 * * @example * ``` * // Expect: { red: 190, green: 198, blue: 212, alpha: 1 } * const example1 = tintSrgbColorValue({ red: 51, green: 102, blue: 153, alpha: 1 }, 0.5) * * // Expect: { red: 51, green: 102, blue: 153, alpha: 1 } * const example2 = tintSrgbColorValue({ red: 51, green: 102, blue: 153, alpha: 1 }, 0) * ``` */ export const tintSrgbColorValue = (color: SrgbColorValue, amount: number): SrgbColorValue => { return internalMixSrgbColorValueWithLinearReference( color, internalOpaqueWhiteLinearRgbColorValue, amount, ) } /** * @description 沿合法路径将一个 sRGB 颜色值向黑色方向推进。 * * @example * ``` * // Expect: { red: 35, green: 73, blue: 111, alpha: 1 } * const example1 = shadeSrgbColorValue({ red: 51, green: 102, blue: 153, alpha: 1 }, 0.5) * * // Expect: { red: 51, green: 102, blue: 153, alpha: 1 } * const example2 = shadeSrgbColorValue({ red: 51, green: 102, blue: 153, alpha: 1 }, 0) * ``` */ export const shadeSrgbColorValue = (color: SrgbColorValue, amount: number): SrgbColorValue => { return internalMixSrgbColorValueWithLinearReference( color, internalOpaqueBlackLinearRgbColorValue, amount, ) } /** * @description 沿合法路径将一个 sRGB 颜色值向中性灰方向推进。 * * @example * ``` * // Expect: { red: 141, green: 153, blue: 171, alpha: 1 } * const example1 = toneSrgbColorValue({ red: 51, green: 102, blue: 153, alpha: 1 }, 0.5) * * // Expect: { red: 51, green: 102, blue: 153, alpha: 1 } * const example2 = toneSrgbColorValue({ red: 51, green: 102, blue: 153, alpha: 1 }, 0) * ``` */ export const toneSrgbColorValue = (color: SrgbColorValue, amount: number): SrgbColorValue => { return internalMixSrgbColorValueWithLinearReference( color, internalOpaqueGrayLinearRgbColorValue, amount, ) } /** * @description 沿合法路径提高一个 sRGB 颜色值的亮度倾向。 * * 该能力当前等价于向白色混合。 * * @example * ``` * // Expect: { red: 143, green: 160, blue: 186, alpha: 1 } * const example1 = lightenSrgbColorValue({ red: 51, green: 102, blue: 153, alpha: 1 }, 0.25) * * // Expect: { red: 51, green: 102, blue: 153, alpha: 1 } * const example2 = lightenSrgbColorValue({ red: 51, green: 102, blue: 153, alpha: 1 }, 0) * ``` */ export const lightenSrgbColorValue = (color: SrgbColorValue, amount: number): SrgbColorValue => { return tintSrgbColorValue(color, amount) } /** * @description 沿合法路径降低一个 sRGB 颜色值的亮度倾向。 * * 该能力当前等价于向黑色混合。 * * @example * ``` * // Expect: { red: 44, green: 89, blue: 134, alpha: 1 } * const example1 = darkenSrgbColorValue({ red: 51, green: 102, blue: 153, alpha: 1 }, 0.25) * * // Expect: { red: 51, green: 102, blue: 153, alpha: 1 } * const example2 = darkenSrgbColorValue({ red: 51, green: 102, blue: 153, alpha: 1 }, 0) * ``` */ export const darkenSrgbColorValue = (color: SrgbColorValue, amount: number): SrgbColorValue => { return shadeSrgbColorValue(color, amount) }