import { internalAssertAlpha } from "../internal.ts" import type { HslColorValue, HsvColorValue, SrgbColorValue } from "../types.ts" import { createHslColorValue, createHsvColorValue, createSrgbColorValue } from "./construct.ts" import { hslColorValueToSrgbColorValue, hsvColorValueToSrgbColorValue } from "./convert.ts" import { internalExpandShortHex, internalHexPattern, internalParsePercentUnit, internalParseRgbChannel, } from "./internal.ts" /** * @description 判断输入是否为合法的十六进制颜色字符串。 * * @example * ``` * // Expect: true * const example1 = isHexColorString("#F0A") * * // Expect: false * const example2 = isHexColorString("#12") * ``` */ export const isHexColorString = (input: string): boolean => { return internalHexPattern.test(input.trim()) } /** * @description 将十六进制颜色字符串解析为 sRGB 颜色值对象。 * * @example * ``` * // Expect: { red: 255, green: 0, blue: 170, alpha: 1 } * const example1 = hexColorStringToSrgbColorValue("#F0A") * * // Expect: { red: 17, green: 34, blue: 51, alpha: 0.2667 } * const example2 = hexColorStringToSrgbColorValue("#11223344") * ``` */ export const hexColorStringToSrgbColorValue = (input: string): SrgbColorValue => { const normalizedInput = input.trim() if (isHexColorString(normalizedInput) === false) { throw new TypeError("Invalid hex color input") } const hex = normalizedInput.startsWith("#") ? normalizedInput.slice(1) : normalizedInput const expandedHex = hex.length === 3 || hex.length === 4 ? internalExpandShortHex(hex) : hex const red = Number.parseInt(expandedHex.slice(0, 2), 16) const green = Number.parseInt(expandedHex.slice(2, 4), 16) const blue = Number.parseInt(expandedHex.slice(4, 6), 16) const alpha = expandedHex.length === 8 ? Number((Number.parseInt(expandedHex.slice(6, 8), 16) / 255).toFixed(4)) : 1 return createSrgbColorValue({ red, green, blue, alpha }) } const internalParseRgbParts = (input: string, allowAlpha: boolean): SrgbColorValue => { const rgbMatch = /^rgba?\((.*)\)$/i.exec(input) if (rgbMatch === null) { throw new TypeError("Invalid color string input") } const partsSource = rgbMatch[1] if (partsSource === undefined) { throw new TypeError("Invalid color string input") } const parts = partsSource.split(",").map((part) => part.trim()) if (allowAlpha === true && parts.length !== 4) { throw new TypeError("Invalid RGBA color string input") } if (allowAlpha === false && parts.length !== 3) { throw new TypeError("Invalid RGB color string input") } const [redPart = "", greenPart = "", bluePart = "", alphaPart] = parts const red = internalParseRgbChannel(redPart, "red") const green = internalParseRgbChannel(greenPart, "green") const blue = internalParseRgbChannel(bluePart, "blue") const alpha = allowAlpha === true ? Number(alphaPart) : 1 return createSrgbColorValue({ red, green, blue, alpha }) } /** * @description 将 `rgb(r, g, b)` 字符串解析为 sRGB 颜色值对象。 * * @example * ``` * // Expect: { red: 51, green: 102, blue: 153, alpha: 1 } * const example1 = rgbColorStringToSrgbColorValue("rgb(51, 102, 153)") * * // Expect: { red: 0, green: 0, blue: 0, alpha: 1 } * const example2 = rgbColorStringToSrgbColorValue("rgb(0, 0, 0)") * ``` */ export const rgbColorStringToSrgbColorValue = (input: string): SrgbColorValue => { return internalParseRgbParts(input.trim(), false) } /** * @description 将 `rgba(r, g, b, a)` 字符串解析为 sRGB 颜色值对象。 * * @example * ``` * // Expect: { red: 51, green: 102, blue: 153, alpha: 0.5 } * const example1 = rgbaColorStringToSrgbColorValue("rgba(51, 102, 153, 0.5)") * * // Expect: { red: 255, green: 255, blue: 255, alpha: 1 } * const example2 = rgbaColorStringToSrgbColorValue("rgba(255, 255, 255, 1)") * ``` */ export const rgbaColorStringToSrgbColorValue = (input: string): SrgbColorValue => { return internalParseRgbParts(input.trim(), true) } const internalParseHslParts = (input: string, allowAlpha: boolean): HslColorValue => { const hslMatch = /^hsla?\((.*)\)$/i.exec(input) if (hslMatch === null) { throw new TypeError("Invalid color string input") } const partsSource = hslMatch[1] if (partsSource === undefined) { throw new TypeError("Invalid color string input") } const parts = partsSource.split(",").map((part) => part.trim()) if (allowAlpha === true && parts.length !== 4) { throw new TypeError("Invalid HSLA color string input") } if (allowAlpha === false && parts.length !== 3) { throw new TypeError("Invalid HSL color string input") } const [huePart = "", saturationPart = "", lightnessPart = "", alphaPart] = parts const hue = Number(huePart) const saturation = internalParsePercentUnit(saturationPart, "saturation") const lightness = internalParsePercentUnit(lightnessPart, "lightness") const alpha = allowAlpha === true ? Number(alphaPart) : 1 internalAssertAlpha(alpha) return createHslColorValue({ hue, saturation, lightness, alpha }) } /** * @description 将 `hsl(h, s%, l%)` 字符串解析为 HSL 颜色值对象。 * * @example * ``` * // Expect: { hue: 210, saturation: 0.5, lightness: 0.4, alpha: 1 } * const example1 = hslColorStringToHslColorValue("hsl(210, 50%, 40%)") * * // Expect: { hue: 330, saturation: 0.5, lightness: 0.4, alpha: 1 } * const example2 = hslColorStringToHslColorValue("hsl(-30, 50%, 40%)") * ``` */ export const hslColorStringToHslColorValue = (input: string): HslColorValue => { return internalParseHslParts(input.trim(), false) } /** * @description 将 `hsla(h, s%, l%, a)` 字符串解析为 HSL 颜色值对象。 * * @example * ``` * // Expect: { hue: 210, saturation: 0.5, lightness: 0.4, alpha: 0.5 } * const example1 = hslaColorStringToHslColorValue("hsla(210, 50%, 40%, 0.5)") * * // Expect: { hue: 0, saturation: 0, lightness: 1, alpha: 1 } * const example2 = hslaColorStringToHslColorValue("hsla(0, 0%, 100%, 1)") * ``` */ export const hslaColorStringToHslColorValue = (input: string): HslColorValue => { return internalParseHslParts(input.trim(), true) } const internalParseHsvParts = (input: string, allowAlpha: boolean): HsvColorValue => { const hsvMatch = /^hsva?\((.*)\)$/i.exec(input) if (hsvMatch === null) { throw new TypeError("Invalid color string input") } const partsSource = hsvMatch[1] if (partsSource === undefined) { throw new TypeError("Invalid color string input") } const parts = partsSource.split(",").map((part) => part.trim()) if (allowAlpha === true && parts.length !== 4) { throw new TypeError("Invalid HSVA color string input") } if (allowAlpha === false && parts.length !== 3) { throw new TypeError("Invalid HSV color string input") } const [huePart = "", saturationPart = "", valuePart = "", alphaPart] = parts const hue = Number(huePart) const saturation = internalParsePercentUnit(saturationPart, "saturation") const value = internalParsePercentUnit(valuePart, "value") const alpha = allowAlpha === true ? Number(alphaPart) : 1 internalAssertAlpha(alpha) return createHsvColorValue({ hue, saturation, value, alpha }) } /** * @description 将 `hsv(h, s%, v%)` 字符串解析为 HSV 颜色值对象。 * * @example * ``` * // Expect: { hue: 210, saturation: 0.6667, value: 0.6, alpha: 1 } * const example1 = hsvColorStringToHsvColorValue("hsv(210, 66.67%, 60%)") * * // Expect: { hue: 30, saturation: 0.5, value: 0.6, alpha: 1 } * const example2 = hsvColorStringToHsvColorValue("hsv(390, 50%, 60%)") * ``` */ export const hsvColorStringToHsvColorValue = (input: string): HsvColorValue => { return internalParseHsvParts(input.trim(), false) } /** * @description 将 `hsva(h, s%, v%, a)` 字符串解析为 HSV 颜色值对象。 * * @example * ``` * // Expect: { hue: 210, saturation: 0.6667, value: 0.6, alpha: 0.5 } * const example1 = hsvaColorStringToHsvColorValue("hsva(210, 66.67%, 60%, 0.5)") * * // Expect: { hue: 0, saturation: 0, value: 1, alpha: 1 } * const example2 = hsvaColorStringToHsvColorValue("hsva(0, 0%, 100%, 1)") * ``` */ export const hsvaColorStringToHsvColorValue = (input: string): HsvColorValue => { return internalParseHsvParts(input.trim(), true) } /** * @description 将异构颜色字符串归一化为 sRGB 颜色值对象。 * * 这是一个面向最终显示层的归一化入口。它会有意把十六进制、RGB、HSL 与 HSV * 等不同语义层的字符串统一折叠为 sRGB;如果调用方需要保留输入本身的颜色语义, * 应改用对应的专用解析函数。 * * @example * ``` * // Expect: { red: 51, green: 102, blue: 153, alpha: 1 } * const example1 = colorStringToSrgbColorValue("#336699") * * // Expect: { red: 51, green: 102, blue: 153, alpha: 1 } * const example2 = colorStringToSrgbColorValue("hsl(210, 50%, 40%)") * ``` */ export const colorStringToSrgbColorValue = (input: string): SrgbColorValue => { const normalizedInput = input.trim() if (normalizedInput.startsWith("#") || isHexColorString(normalizedInput) === true) { return hexColorStringToSrgbColorValue(normalizedInput) } if (normalizedInput.toLowerCase().startsWith("rgba(")) { return rgbaColorStringToSrgbColorValue(normalizedInput) } if (normalizedInput.toLowerCase().startsWith("rgb(")) { return rgbColorStringToSrgbColorValue(normalizedInput) } if (normalizedInput.toLowerCase().startsWith("hsla(")) { return hslColorValueToSrgbColorValue(hslaColorStringToHslColorValue(normalizedInput)) } if (normalizedInput.toLowerCase().startsWith("hsl(")) { return hslColorValueToSrgbColorValue(hslColorStringToHslColorValue(normalizedInput)) } if (normalizedInput.toLowerCase().startsWith("hsva(")) { return hsvColorValueToSrgbColorValue(hsvaColorStringToHsvColorValue(normalizedInput)) } if (normalizedInput.toLowerCase().startsWith("hsv(")) { return hsvColorValueToSrgbColorValue(hsvColorStringToHsvColorValue(normalizedInput)) } throw new TypeError("Invalid color string input") }