import { internalAssertAlpha, internalAssertColorUnit, internalAssertFinite, internalRoundColorFloat, } from "../internal.ts" import type { HslColorValue, HsvColorValue, LinearRgbColorValue, SrgbColorValue } from "../types.ts" /** * @description 匹配模块支持的十六进制颜色字符串格式。 */ export const internalHexPattern: RegExp = /^#?(?:[\da-fA-F]{3}|[\da-fA-F]{4}|[\da-fA-F]{6}|[\da-fA-F]{8})$/ /** * @description 断言一个 sRGB 通道值是合法的 8 位整数。 */ export const internalAssertByte = (value: number, channelName: string): void => { if (Number.isInteger(value) === false || value < 0 || value > 255) { throw new RangeError(`Color channel ${channelName} must be an integer between 0 and 255`) } } /** * @description 将色相角度规范化到 `0` 到 `360` 之间,`360` 会被折叠为 `0`。 */ export const internalNormalizeHue = (value: number): number => { internalAssertFinite(value, "Color hue") const normalizedValue = value % 360 if (normalizedValue < 0) { return normalizedValue + 360 } return normalizedValue } /** * @description 规范化 Linear RGB 颜色值结构。 */ export const internalNormalizeLinearRgbColorValue = ( color: LinearRgbColorValue, ): LinearRgbColorValue => { internalAssertFinite(color.red, "Color channel red") internalAssertFinite(color.green, "Color channel green") internalAssertFinite(color.blue, "Color channel blue") internalAssertAlpha(color.alpha) return { red: color.red, green: color.green, blue: color.blue, alpha: color.alpha, } } /** * @description 规范化 sRGB 颜色值结构。 */ export const internalNormalizeSrgbColorValue = (color: SrgbColorValue): SrgbColorValue => { internalAssertByte(color.red, "red") internalAssertByte(color.green, "green") internalAssertByte(color.blue, "blue") internalAssertAlpha(color.alpha) return { red: color.red, green: color.green, blue: color.blue, alpha: color.alpha, } } /** * @description 规范化 HSL 颜色值结构。 */ export const internalNormalizeHslColorValue = (color: HslColorValue): HslColorValue => { internalAssertColorUnit(color.saturation, "saturation") internalAssertColorUnit(color.lightness, "lightness") internalAssertAlpha(color.alpha) return { hue: internalNormalizeHue(color.hue), saturation: internalRoundColorFloat(color.saturation), lightness: internalRoundColorFloat(color.lightness), alpha: color.alpha, } } /** * @description 规范化 HSV 颜色值结构。 */ export const internalNormalizeHsvColorValue = (color: HsvColorValue): HsvColorValue => { internalAssertColorUnit(color.saturation, "saturation") internalAssertColorUnit(color.value, "value") internalAssertAlpha(color.alpha) return { hue: internalNormalizeHue(color.hue), saturation: internalRoundColorFloat(color.saturation), value: internalRoundColorFloat(color.value), alpha: color.alpha, } } /** * @description 将短格式十六进制颜色扩展为长格式。 */ export const internalExpandShortHex = (input: string): string => { return input .split("") .map((char) => `${char}${char}`) .join("") } /** * @description 将 8 位通道值格式化为两位十六进制字符串。 */ export const internalFormatHexByte = (value: number): string => { return value.toString(16).padStart(2, "0").toUpperCase() } /** * @description 将浮点数格式化为适合颜色字符串的紧凑小数字符串。 */ export const internalFormatAlpha = (value: number): string => { return Number(value.toFixed(4)).toString() } /** * @description 将 `0` 到 `1` 的单位值格式化为百分比数值字符串,不附带 `%` 符号。 */ export const internalFormatPercentUnit = (value: number): string => { internalAssertColorUnit(value, "unit") return Number((value * 100).toFixed(2)).toString() } /** * @description 将 RGB 字符串通道解析为合法的 8 位通道值。 */ export const internalParseRgbChannel = (input: string, channelName: string): number => { const value = Number(input) internalAssertByte(value, channelName) return value } /** * @description 将百分比字符串解析为 `0` 到 `1` 之间的单位值。 */ export const internalParsePercentUnit = (input: string, channelName: string): number => { if (input.endsWith("%") === false) { throw new TypeError(`Invalid ${channelName} percentage input`) } return internalRoundColorFloat(Number(input.slice(0, -1)) / 100) } /** * @description 将 8 位 sRGB 通道值转换为线性光通道值。 */ export const internalGetLinearRgbChannelBySrgbChannel = (value: number): number => { internalAssertByte(value, "sRGB") const normalizedValue = value / 255 if (normalizedValue <= 0.040_45) { return internalRoundColorFloat(normalizedValue / 12.92) } return internalRoundColorFloat(((normalizedValue + 0.055) / 1.055) ** 2.4) } /** * @description 将线性光通道值转换为 8 位 sRGB 通道值。 */ export const internalGetSrgbChannelByLinearRgbChannel = ( value: number, channelName: string, ): number => { internalAssertFinite(value, `Linear RGB channel ${channelName}`) if (value < 0 || value > 1) { throw new RangeError( `Linear RGB channel ${channelName} must be between 0 and 1 to convert to sRGB`, ) } if (value <= 0.003_130_8) { return Math.round(value * 12.92 * 255) } return Math.round((1.055 * value ** (1 / 2.4) - 0.055) * 255) } /** * @description 根据输入字符串稳定生成一个 sRGB 颜色值。 */ export const internalGetStableSrgbColorValueByString = (input: string): SrgbColorValue => { let codePointSum = 0 for (const char of input) { const codePoint = char.codePointAt(0) if (codePoint !== undefined) { codePointSum = codePointSum + codePoint } } return { red: Math.round(Math.abs(Math.sin(codePointSum) * 255)), green: Math.round(Math.abs(Math.sin(codePointSum + 1) * 255)), blue: Math.round(Math.abs(Math.sin(codePointSum + 2) * 255)), alpha: 1, } }