Initial commit of akmon project

This commit is contained in:
2026-01-20 08:04:15 +08:00
commit 77a2bab985
1309 changed files with 343305 additions and 0 deletions

View File

@@ -0,0 +1,680 @@
// @ts-nocheck
import { numberInputToObject, rgbaToHex, rgbToHex, rgbToHsl, rgbToHsv } from './conversion';
import { names } from './css-color-names';
import { inputToRGB } from './format-input';
import { HSL, HSLA, HSV, HSVA, HSBA, RGB, RGBA, RGBAString, LColorInfo, LColorFormats, LColorOptions, LColorInput } from '../utssdk/interface.uts';
import { bound01, boundAlpha, clamp01, toBoolean, isNumber } from './util';
export class TinyColor {
r : number;
g : number;
b : number;
a : number;
/** 用于创建 limeColor 实例的格式 */
format ?: LColorFormats;
/** 传递给构造函数以创建 limeColor 实例的输入 */
originalInput : LColorInput;
/** 颜色已被成功解析 */
isValid : boolean;
gradientType ?: string;
/** rounded alpha */
roundA : number;
// #ifdef APP
reversedNames : Map<string, string>;
// #endif
constructor(color : LColorInput = '', opts : LColorOptions = {} as LColorOptions) {
let _color : any = color
// if(color instanceof TinyColor){
// return color as TinyColor
// }
if (isNumber(color)) {
_color = numberInputToObject(color as number);
}
this.originalInput = _color;
const rgb = inputToRGB(_color);
this.r = rgb.r;
this.g = rgb.g;
this.b = rgb.b;
this.a = rgb.a;
this.roundA = Math.round(100 * this.a) / 100;
this.format = opts.format ?? rgb.format;
this.gradientType = opts.gradientType;
// 不要让范围在 [0,255] 中的值返回成 [0,1]。
// 这里可能会失去一些精度,但可以解决原来
// .5 被解释为总数的半数而不是1的一半的问题
// 如果本来应该是128那么这个已经在 inputToRgb 中处理过了 if (this.r < 1) {
if (this.r < 1) {
this.r = Math.round(this.r);
}
if (this.g < 1) {
this.g = Math.round(this.g);
}
if (this.b < 1) {
this.b = Math.round(this.b);
}
this.isValid = rgb.ok ?? false;
// #ifdef APP
this.reversedNames = new Map<string, string>()
names.forEach((value : string, key : string) => {
this.reversedNames.set(value, key)
})
// #endif
}
/**
* 判断当前颜色是否为暗色。
* @returns 一个布尔值,表示当前颜色是否为暗色。
*/
isDark() : boolean {
return this.getBrightness() < 128;
}
/**
* 判断当前颜色是否为亮色。
* @returns 一个布尔值,表示当前颜色是否为亮色。
*/
isLight() : boolean {
return !this.isDark();
}
/**
* 计算当前颜色的亮度值。
* 亮度值是根据 RGB 颜色空间中的红、绿、蓝三个通道的值计算得出的,计算公式为:(r * 299 + g * 587 + b * 114) / 1000。
* @returns 返回颜色的感知亮度范围从0-255。
*/
getBrightness() : number {
// http://www.w3.org/TR/AERT#color-contrast
const rgb = this.toRgb();
return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
}
/**
* 计算当前颜色的相对亮度值。
* 相对亮度值是根据 RGB 颜色空间中的红、绿、蓝三个通道的值计算得出的计算公式为0.2126 * R + 0.7152 * G + 0.0722 * B。
* @returns 返回颜色的感知亮度范围从0-1。
*/
getLuminance() : number {
// http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
const rgb = this.toRgb();
let R : number;
let G : number;
let B : number;
const RsRGB : number = rgb.r / 255;
const GsRGB : number = rgb.g / 255;
const BsRGB : number = rgb.b / 255;
if (RsRGB <= 0.03928) {
R = RsRGB / 12.92;
} else {
// eslint-disable-next-line prefer-exponentiation-operator
R = Math.pow((RsRGB + 0.055) / 1.055, 2.4);
}
if (GsRGB <= 0.03928) {
G = GsRGB / 12.92;
} else {
// eslint-disable-next-line prefer-exponentiation-operator
G = Math.pow((GsRGB + 0.055) / 1.055, 2.4);
}
if (BsRGB <= 0.03928) {
B = BsRGB / 12.92;
} else {
// eslint-disable-next-line prefer-exponentiation-operator
B = Math.pow((BsRGB + 0.055) / 1.055, 2.4);
}
return 0.2126 * R + 0.7152 * G + 0.0722 * B;
}
/**
* 获取当前颜色的透明度值。
* 透明度值的范围是 0 到 1其中 0 表示完全透明1 表示完全不透明。
* @returns 一个数字,表示当前颜色的透明度值。
*/
getAlpha() : number {
return this.a;
}
/**
* 设置当前颜色的透明度值。
* @param alpha - 要设置的透明度值。透明度值的范围是 0 到 1其中 0 表示完全透明1 表示完全不透明。
* @returns 一个 `TinyColor` 对象,表示设置透明度后的颜色。
*/
setAlpha(alpha ?: string) : TinyColor
setAlpha(alpha ?: number) : TinyColor
setAlpha(alpha ?: any) : TinyColor {
this.a = boundAlpha(alpha);
this.roundA = Math.round(100 * this.a) / 100;
return this;
}
/**
* 判断当前颜色是否为单色。
* 单色是指颜色的饱和度S为 0 的颜色这些颜色只有明度L变化没有颜色变化。
* @returns 一个布尔值,表示当前颜色是否为单色。
*/
isMonochrome() : boolean {
const { s } = this.toHsl();
return s == 0;
}
/**
* 将当前颜色转换为 HSV色相、饱和度、亮度颜色空间。
* @returns 一个对象,包含四个属性:`h`(色相)、`s`(饱和度)、`v`(亮度)和 `a`(透明度)。
*/
toHsv() : HSVA {
const hsv = rgbToHsv(this.r, this.g, this.b);
return { h: hsv.h * 360, s: hsv.s, v: hsv.v, a: this.a } as HSVA;
}
/**
* 将当前颜色转换为 HSV色相、饱和度、亮度颜色空间的字符串表示。
* @returns 一个字符串,表示当前颜色的 HSV 或 HSVA 格式 hsva(xxx, xxx, xxx, xx)。
*/
toHsvString() : string {
const hsv = rgbToHsv(this.r, this.g, this.b);
const h = Math.round(hsv.h * 360);
const s = Math.round(hsv.s * 100);
const v = Math.round(hsv.v * 100);
return this.a == 1 ? `hsv(${h}, ${s}%, ${v}%)` : `hsva(${h}, ${s}%, ${v}%, ${this.roundA})`;
}
/**
* 将当前颜色对象转换为HSBA颜色空间,即Hue色相、Saturation饱和度、Brightness亮度和Alpha透明度
* @returns {HSBA} 返回一个HSBA对象表示当前颜色对象在HSBA颜色空间中的值
*/
toHsb() : HSBA {
const hsv = rgbToHsv(this.r, this.g, this.b);
return { h: hsv.h * 360, s: hsv.s, b: hsv.v, a: this.a } as HSBA;
}
/**
* 将当前颜色对象转换为CSS风格的HSB或HSVA字符串
* @returns {string} 返回一个CSS风格的HSB或HSVA字符串表示当前颜色对象的颜色值
*/
toHsbString() : string {
const hsb = this.toHsb();
const h = Math.round(hsb.h);
const s = Math.round(hsb.s * 100);
const b = Math.round(hsb.b * 100);
return this.a == 1
? `hsb(${h}, ${s}%, ${b}%)`
: `hsva(${h}, ${s}%, ${b}%, ${this.roundA})`;
}
/**
* 将当前颜色转换为 HSL色相、饱和度、明度颜色空间。
* @returns 一个对象,包含四个属性:`h`(色相)、`s`(饱和度)、`l`(明度)和 `a`(透明度)。
*/
toHsl() : HSLA {
const hsl = rgbToHsl(this.r, this.g, this.b);
return { h: hsl.h * 360, s: hsl.s, l: hsl.l, a: this.a } as HSLA;
}
/**
* 将当前颜色转换为 HSL色相、饱和度、明度颜色空间的字符串表示。
* @returns 一个字符串,表示当前颜色的 HSL 或 HSLA 格式 hsla(xxx, xxx, xxx, xx)。
*/
toHslString() : string {
const hsl = rgbToHsl(this.r, this.g, this.b);
const h = Math.round(hsl.h * 360);
const s = Math.round(hsl.s * 100);
const l = Math.round(hsl.l * 100);
return this.a == 1 ? `hsl(${h}, ${s}%, ${l}%)` : `hsla(${h}, ${s}%, ${l}%, ${this.roundA})`;
}
/**
* 将当前颜色转换为十六进制颜色表示。
* @param allow3Char 是否允许返回简写的十六进制颜色表示(如果可能)。默认值为 `false`。
* @returns 一个字符串,表示当前颜色的十六进制格式。
*/
toHex(allow3Char = false) : string {
return rgbToHex(this.r, this.g, this.b, allow3Char);
}
/**
* 将当前颜色转换为带有井号(`#`)前缀的十六进制颜色表示。
* @param allow3Char 是否允许返回简写的十六进制颜色表示(如果可能)。默认值为 `false`。
* @returns 一个字符串,表示当前颜色的带有井号前缀的十六进制格式。
*/
toHexString(allow3Char = false) : string {
return '#' + this.toHex(allow3Char);
}
/**
* 返回颜色的八位十六进制值.
* @param allow4Char 如果可能的话将十六进制值缩短为4个字符
*/
toHex8(allow4Char = false) : string {
return rgbaToHex(this.r, this.g, this.b, this.a, allow4Char);
}
/**
* 返回颜色的八位十六进制值,并且值前面带有#符号.
* @param allow4Char 如果可能的话将十六进制值缩短为4个字符
*/
toHex8String(allow4Char = false) : string {
return '#' + this.toHex8(allow4Char);
}
/**
* 根据颜色的透明度Alpha值返回较短的十六进制值并且值前面带有#符号。
* @param allowShortChar 如果可能的话将十六进制值缩短至3个或4个字符
*/
toHexShortString(allowShortChar = false) : string {
return this.a == 1 ? this.toHexString(allowShortChar) : this.toHex8String(allowShortChar);
}
/**
* 将当前颜色转换为 RGB红、绿、蓝颜色空间的对象表示。
* @returns 一个包含 `r`、`g`、`b` 和 `a` 属性的对象,表示当前颜色的 RGB 格式。
*/
toRgb() : RGBA {
return {
r: Math.round(this.r),
g: Math.round(this.g),
b: Math.round(this.b),
a: this.a,
} as RGBA;
}
/**
* 将当前颜色对象转换为CSS风格的RGB或RGBA字符串
* @returns {string} 返回一个CSS风格的RGB或RGBA字符串表示当前颜色对象的颜色值
*/
toRgbString() : string {
const r = Math.round(this.r);
const g = Math.round(this.g);
const b = Math.round(this.b);
return this.a == 1 ? `rgb(${r}, ${g}, ${b})` : `rgba(${r}, ${g}, ${b}, ${this.roundA})`;
}
/**
* 将当前颜色转换为百分比表示的 RGB红、绿、蓝颜色空间的对象表示。
* @returns 一个包含 `r`、`g`、`b` 和 `a` 属性的对象,表示当前颜色的百分比表示的 RGB 格式。
*/
toPercentageRgb() : RGBAString {
// 定义一个格式化函数,将颜色值转换为百分比表示
const fmt = (x : number) : string => `${Math.round(bound01(x, 255) * 100)}%`;
// 返回一个RGBA对象其中颜色值已转换为百分比表示
return {
r: fmt(this.r),
g: fmt(this.g),
b: fmt(this.b),
a: this.a,
} as RGBAString;
}
/**
* 将RGBA相对值插值为一个字符串颜色值以百分比表示。
*/
toPercentageRgbString() : string {
// 定义一个四舍五入函数,将颜色值转换为百分比表示的整数
const rnd = (x : number) : number => Math.round(bound01(x, 255) * 100);
// 根据alpha值返回不同的字符串表示
return this.a == 1
? `rgb(${rnd(this.r)}%, ${rnd(this.g)}%, ${rnd(this.b)}%)`
: `rgba(${rnd(this.r)}%, ${rnd(this.g)}%, ${rnd(this.b)}%, ${this.roundA})`;
}
/**
* 返回这个颜色的'真实'名称,不存在返回null
*/
toName() : string | null {
if (this.a == 0) {
return 'transparent';
}
if (this.a < 1) {
return null;
}
const hex = this.toHexString()//'#' + rgbToHex(this.r, this.g, this.b, false);
// #ifndef APP
const _names = Array.from(names.entries())
for (let [key, value] of _names) {
if (hex == value) {
return key;
}
}
return null;
// #endif
// #ifdef APP
return this.reversedNames.get(hex)
// #endif
}
/**
* 将颜色转换为字符串表示。
*
* @param format - 用于显示字符串表示的格式。
*/
// toString<T extends 'name'>(format : T) : string;
// toString<T extends LColorFormats>(format ?: T) : string;
// #ifdef APP-ANDROID
override toString() : string {
return this.toString(null)
}
// #endif
toString(format ?: LColorFormats) : string {
const formatSet = toBoolean(format);
let _format = format ?? this.format;
let formattedString : string | null = null;
const hasAlpha = this.a < 1 && this.a >= 0;
const needsAlphaFormat = !formatSet && hasAlpha && (_format != null && _format.startsWith('hex') || _format == 'name');
if (needsAlphaFormat) {
// 特殊情况透明度所有其他非透明度格式都会在有透明度时返回rgba。
// 当透明度为0时返回"transparent"。
if (_format == 'name' && this.a == 0) {
return this.toName() ?? 'transparent';
}
return this.toRgbString();
}
if (_format == 'rgb') {
formattedString = this.toRgbString();
}
if (_format == 'prgb') {
formattedString = this.toPercentageRgbString();
}
if (_format == 'hex' || _format == 'hex6') {
formattedString = this.toHexString();
}
if (_format == 'hex3') {
formattedString = this.toHexString(true);
}
if (_format == 'hex4') {
formattedString = this.toHex8String(true);
}
if (_format == 'hex8') {
formattedString = this.toHex8String();
}
if (_format == 'name') {
formattedString = this.toName();
}
if (_format == 'hsl') {
formattedString = this.toHslString();
}
if (_format == 'hsv') {
formattedString = this.toHsvString();
}
if (_format == 'hsb') {
formattedString = this.toHsbString();
}
return formattedString ?? this.toHexString();
}
toNumber() : number {
return (Math.round(this.r) << 16) + (Math.round(this.g) << 8) + Math.round(this.b);
}
clone() : TinyColor {
return new TinyColor(this.toString());
}
/**
* 将颜色变浅指定的量。提供100将始终返回白色。
* @param amount - 有效值介于1-100之间
*/
lighten(amount = 10) : TinyColor {
const hsl = this.toHsl();
hsl.l += amount / 100;
hsl.l = clamp01(hsl.l);
return new TinyColor(hsl, { format: this.format } as LColorOptions);
}
/**
* 将颜色变亮一定的量范围从0到100。
* @param amount - 有效值在1-100之间
*/
brighten(amount = 10) : TinyColor {
const rgb = this.toRgb();
rgb.r = Math.max(0, Math.min(255, rgb.r - Math.round(255 * -(amount / 100))));
rgb.g = Math.max(0, Math.min(255, rgb.g - Math.round(255 * -(amount / 100))));
rgb.b = Math.max(0, Math.min(255, rgb.b - Math.round(255 * -(amount / 100))));
return new TinyColor(rgb, { format: this.format } as LColorOptions);
}
/**
* 将颜色变暗一定的量范围从0到100。
* 提供100将始终返回黑色。
* @param amount - 有效值在1-100之间
*/
darken(amount = 10) : TinyColor {
const hsl = this.toHsl();
hsl.l -= amount / 100;
hsl.l = clamp01(hsl.l);
return new TinyColor(hsl, { format: this.format } as LColorOptions);
}
/**
* 将颜色与纯白色混合范围从0到100。
* 提供0将什么都不做提供100将始终返回白色。
* @param amount - 有效值在1-100之间
*/
tint(amount = 10) : TinyColor {
return this.mix('white', amount);
}
/**
* 将颜色与纯黑色混合范围从0到100。
* 提供0将什么都不做提供100将始终返回黑色。
* @param amount - 有效值在1-100之间
*/
shade(amount = 10) : TinyColor {
return this.mix('black', amount);
}
/**
* 将颜色的饱和度降低一定的量范围从0到100。
* 提供100与调用greyscale相同
* @param amount - 有效值在1-100之间
*/
desaturate(amount = 10) : TinyColor {
const hsl = this.toHsl();
hsl.s -= amount / 100;
hsl.s = clamp01(hsl.s);
return new TinyColor(hsl, { format: this.format } as LColorOptions);
}
/**
* 将颜色饱和度提高一定数量,范围从 0 到 100。
* @param amount - 有效值介于 1 到 100 之间。
*/
saturate(amount = 10) : TinyColor {
const hsl = this.toHsl();
hsl.s += amount / 100;
hsl.s = clamp01(hsl.s);
return new TinyColor(hsl, { format: this.format } as LColorOptions);
}
/**
* 将颜色完全去饱和为灰度。
* 等同于调用 `desaturate(100)`。
*/
greyscale() : TinyColor {
return this.desaturate(100);
}
/**
* spin 方法接收一个正数或负数作为参数,表示色相的变化量,变化范围在 [-360, 360] 之间。
* 如果提供的值超出此范围,它将被限制在此范围内。
*/
spin(amount : number) : TinyColor {
const hsl = this.toHsl();
const hue = (hsl.h + amount) % 360;
hsl.h = hue < 0 ? 360 + hue : hue;
return new TinyColor(hsl, { format: this.format } as LColorOptions);
}
/**
* 将当前颜色与另一种颜色按给定的比例混合范围从0到100。
* 0表示不混合返回当前颜色
*/
mix(color : LColorInput, amount = 50) : TinyColor {
const rgb1 = this.toRgb();
const rgb2 = new TinyColor(color).toRgb();
const p = amount / 100;
const rgba = {
r: (rgb2.r - rgb1.r) * p + rgb1.r,
g: (rgb2.g - rgb1.g) * p + rgb1.g,
b: (rgb2.b - rgb1.b) * p + rgb1.b,
a: (rgb2.a - rgb1.a) * p + rgb1.a,
};
return new TinyColor(rgba, { format: this.format } as LColorOptions);
}
/**
* 生成一组与当前颜色相似的颜色。
* 这些颜色在色相环上是相邻的,形成一个类似于彩虹的颜色序列。
* @param results - 要生成的相似颜色的数量,默认值为 6。
* @param slices - 将色相环划分为多少个部分,默认值为 30。
* @returns 一个包含当前颜色及其相似颜色的 TinyColor 对象数组。
*/
analogous(results = 6, slices = 30) : TinyColor[] {
const hsl = this.toHsl();
const part = 360 / slices;
const ret : TinyColor[] = [this];
let _results = results
// for (hsl.h = (hsl.h - ((part * results) >> 1) + 720) % 360; --results;) {
// hsl.h = (hsl.h + part) % 360;
// ret.push(new TinyColor(hsl));
// }
hsl.h = (hsl.h - ((part * _results) >> 1) + 720) % 360;
while (_results > 0) {
hsl.h = (hsl.h + part) % 360;
ret.push(new TinyColor(hsl));
_results--;
}
return ret;
}
/**
* 计算当前颜色的补色。
* 补色是指在色相环上相对位置的颜色,它们的色相差为 180°。
* taken from https://github.com/infusion/jQuery-xcolor/blob/master/jquery.xcolor.js
* @returns 一个 TinyColor 对象,表示当前颜色的补色。
*/
complement() : TinyColor {
const hsl = this.toHsl();
hsl.h = (hsl.h + 180) % 360;
return new TinyColor(hsl, { format: this.format } as LColorOptions);
}
/**
* 生成一组与当前颜色具有相同色相和饱和度的颜色。
* 这些颜色的亮度值不同,形成一个单色调的颜色序列。
* @param results - 要生成的单色调颜色的数量,默认值为 6。
* @returns 一个包含当前颜色及其单色调颜色的 TinyColor 对象数组。
*/
monochromatic(results = 6) : TinyColor[] {
const hsv = this.toHsv();
const { h } = hsv;
const { s } = hsv;
let { v } = hsv;
const res : TinyColor[] = [];
const modification = 1 / results;
let _results = results
// while (results--) {
// res.push(new TinyColor({ h, s, v }));
// v = (v + modification) % 1;
// }
while (_results > 0) {
res.push(new TinyColor({ h, s, v }));
v = (v + modification) % 1;
_results--
}
return res;
}
/**
* 生成当前颜色的分裂补色。
* 分裂补色是指在色相环上位于当前颜色的两侧的颜色,它们的色相差为 180°。
* @returns 一个包含当前颜色及其分裂补色的 TinyColor 对象数组。
*/
splitcomplement() : TinyColor[] {
const hsl = this.toHsl();
const { h } = hsl;
return [
this,
new TinyColor({ h: (h + 72) % 360, s: hsl.s, l: hsl.l }),
new TinyColor({ h: (h + 216) % 360, s: hsl.s, l: hsl.l }),
] as TinyColor[];
}
/**
* 计算当前颜色在给定背景颜色上的显示效果。
* @param background - 背景颜色,可以是任何 LColorInput 类型的值。
* @returns 一个 TinyColor 对象,表示当前颜色在给定背景颜色上的显示效果。
*/
onBackground(background : LColorInput) : TinyColor {
const fg = this.toRgb();
const bg = new TinyColor(background).toRgb();
const alpha = fg.a + bg.a * (1 - fg.a);
return new TinyColor({
r: (fg.r * fg.a + bg.r * bg.a * (1 - fg.a)) / alpha,
g: (fg.g * fg.a + bg.g * bg.a * (1 - fg.a)) / alpha,
b: (fg.b * fg.a + bg.b * bg.a * (1 - fg.a)) / alpha,
a: alpha,
});
}
/**
* 生成当前颜色的三色调。
* 三色调是指在色相环上位于当前颜色的两侧的颜色,它们的色相差为 120°。
* 这是 `polyad(3)` 方法的别名。
* @returns 一个包含当前颜色及其三色调颜色的 TinyColor 对象数组。
*/
triad() : TinyColor[] {
return this.polyad(3);
}
/**
* 生成当前颜色的四色调。
* 四色调是指在色相环上位于当前颜色的两侧的颜色,它们的色相差为 90°。
* 这是 `polyad(4)` 方法的别名。
* @returns 一个包含当前颜色及其四色调颜色的 TinyColor 对象数组。
*/
tetrad() : TinyColor[] {
return this.polyad(4);
}
/**
* 生成当前颜色的 n 色调。
* n 色调是指在色相环上位于当前颜色的两侧的颜色,它们的色相差为 360° / n。
* Get polyad colors, like (for 1, 2, 3, 4, 5, 6, 7, 8, etc...)
* monad, dyad, triad, tetrad, pentad, hexad, heptad, octad, etc...
* @param n - 一个整数,表示要生成的色调数量。
* @returns 一个包含当前颜色及其 n 色调颜色的 TinyColor 对象数组。
*/
polyad(n : number) : TinyColor[] {
const hsl = this.toHsl();
const { h } = hsl;
const result : TinyColor[] = [this];
const increment = 360 / n;
for (let i = 1; i < n; i++) {
result.push(new TinyColor({ h: (h + i * increment) % 360, s: hsl.s, l: hsl.l }));
}
return result;
}
/**
* 比较当前颜色与给定颜色是否相等。
* @param color - 一个 LColorInput 类型的值,表示要比较的颜色。
* @returns 一个布尔值,表示当前颜色与给定颜色是否相等。
*/
// #ifndef APP-ANDROID
equals(other ?: LColorInput) : boolean {
if (other == null) {
return false
} else if (other instanceof TinyColor) {
return this.toRgbString() == (other as TinyColor).toRgbString()
}
return this.toRgbString() == new TinyColor(other).toRgbString();
}
// #endif
// #ifdef APP-ANDROID
override equals(other ?: LColorInput) : boolean {
if (other == null) {
return false
} else if (other instanceof TinyColor) {
return this.toRgbString() == (other as TinyColor).toRgbString()
}
return this.toRgbString() == new TinyColor(other).toRgbString();
}
// #endif
}
export function tinyColor(color : LColorInput = '', opts : LColorOptions = {} as LColorOptions) : TinyColor {
return new TinyColor(color, opts);
}

View File

@@ -0,0 +1,306 @@
import { RGB,HSL,HSV } from '../utssdk/interface.uts';
import { bound01, pad2 } from './util';
/**
* Handle bounds / percentage checking to conform to CSS color spec
* 处理边界/百分比检查以符合 CSS 颜色规范
* <http://www.w3.org/TR/css3-color/>
* *Assumes:* r, g, b in [0, 255] or [0, 1]
* *Returns:* { r, g, b } in [0, 255]
*/
function rgbToRgb(r: string, g: string, b: string):RGB;
function rgbToRgb(r: number, g: string, b: string):RGB;
function rgbToRgb(r: number, g: number, b: string):RGB;
function rgbToRgb(r: number, g: number, b: number):RGB;
function rgbToRgb(r: any, g: any, b: any) : RGB {
return {
r: bound01(r, 255) * 255,
g: bound01(g, 255) * 255,
b: bound01(b, 255) * 255,
} as RGB;
}
export {
rgbToRgb
}
/**
* Converts an RGB color value to HSL.
* 将 RGB 颜色值转换为 HSL。
* *Assumes:* r, g, and b are contained in [0, 255] or [0, 1]
* *Returns:* { h, s, l } in [0,1]
*/
function rgbToHsl(r: string, g: string, b: string):HSL;
function rgbToHsl(r: number, g: string, b: string):HSL;
function rgbToHsl(r: number, g: number, b: string):HSL;
function rgbToHsl(r: number, g: number, b: number):HSL;
function rgbToHsl(r : any, g : any, b : any) : HSL {
r = bound01(r, 255);
g = bound01(g, 255);
b = bound01(b, 255);
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h = 0;
let s:number// = 0;
const l = (max + min) / 2;
if (max == min) {
s = 0;
h = 0; // achromatic
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
default:
console.log('h')
break;
}
h /= 6;
}
return { h, s, l } as HSL;
}
export {
rgbToHsl
}
export function hue2rgb(p : number, q : number, t : number) : number {
let _t = t
if (_t < 0) {
_t += 1;
}
if (_t > 1) {
_t -= 1;
}
if (_t < 1 / 6) {
return p + (q - p) * (6 * _t);
}
if (_t < 1 / 2) {
return q;
}
if (_t < 2 / 3) {
return p + (q - p) * (2 / 3 - _t) * 6;
}
return p;
}
/**
* Converts an HSL color value to RGB.
* 将 HSL 颜色值转换为 RGB。
* *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100]
* *Returns:* { r, g, b } in the set [0, 255]
*/
function hslToRgb(h : string,s : string,l : string) : RGB
function hslToRgb(h : number,s : string,l : string) : RGB
function hslToRgb(h : number,s : number,l : string) : RGB
function hslToRgb(h : number,s : number,l : number) : RGB
function hslToRgb(h : any,s : any,l : any) : RGB {
let r : number;
let g : number;
let b : number;
h = bound01(h, 360);
s = bound01(s, 100);
l = bound01(l, 100);
if (s == 0) {
// achromatic
g = l;
b = l;
r = l;
} else {
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return { r: r * 255, g: g * 255, b: b * 255 } as RGB;
}
export {
hslToRgb
}
/**
* Converts an RGB color value to HSV
* 将RGB颜色值转换为HSV颜色值
* *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]
* *Returns:* { h, s, v } in [0,1]
*/
export function rgbToHsv(r : number, g : number, b : number) : HSV {
r = bound01(r, 255);
g = bound01(g, 255);
b = bound01(b, 255);
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h = 0;
const v = max;
const d = max - min;
const s = max == 0 ? 0 : d / max;
if (max == min) {
h = 0; // achromatic
} else {
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
default:
console.log('1')
break;
}
h /= 6;
}
return { h, s, v } as HSV;
}
/**
* Converts an HSV color value to RGB.
* 将HSV颜色值转换为RGB。
* *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
* *Returns:* { r, g, b } in the set [0, 255]
*/
function hsvToRgb( h : string, s : string, v : string) : RGB
function hsvToRgb( h : number, s : string, v : string) : RGB
function hsvToRgb( h : number, s : number, v : string) : RGB
function hsvToRgb( h : number, s : number, v : number) : RGB
function hsvToRgb( h : any, s : any, v : any) : RGB {
h = bound01(h, 360) * 6;
s = bound01(s, 100);
v = bound01(v, 100);
const i = Math.floor(h);
const f = h - i;
const p = v * (1 - s);
const q = v * (1 - f * s);
const t = v * (1 - (1 - f) * s);
const mod = i % 6;
const r = [v, q, p, p, t, v][mod];
const g = [t, v, v, q, p, p][mod];
const b = [p, p, t, v, v, q][mod];
return { r: r * 255, g: g * 255, b: b * 255 } as RGB;
}
export {
hsvToRgb
}
/**
* Converts an RGB color to hex
* 将RGB颜色转换为十六进制。
* Assumes r, g, and b are contained in the set [0, 255]
* Returns a 3 or 6 character hex
*/
export function rgbToHex(r : number, g : number, b : number, allow3Char : boolean = false) : string {
const hex = [
pad2(Math.round(r).toString(16)),
pad2(Math.round(g).toString(16)),
pad2(Math.round(b).toString(16)),
];
// Return a 3 character hex if possible
if (
allow3Char &&
hex[0].startsWith(hex[0].charAt(1)) &&
hex[1].startsWith(hex[1].charAt(1)) &&
hex[2].startsWith(hex[2].charAt(1))
) {
return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
}
return hex.join('');
}
/**
* Converts an RGBA color plus alpha transparency to hex
* 将带有透明度的RGBA颜色转换为十六进制。
* Assumes r, g, b are contained in the set [0, 255] and
* a in [0, 1]. Returns a 4 or 8 character rgba hex
*/
// eslint-disable-next-line max-params
export function rgbaToHex(r : number, g : number, b : number, a : number, allow4Char : boolean = false) : string {
const hex = [
pad2(Math.round(r).toString(16)),
pad2(Math.round(g).toString(16)),
pad2(Math.round(b).toString(16)),
pad2(convertDecimalToHex(a)),
];
// Return a 4 character hex if possible
if (
allow4Char &&
hex[0].startsWith(hex[0].charAt(1)) &&
hex[1].startsWith(hex[1].charAt(1)) &&
hex[2].startsWith(hex[2].charAt(1)) &&
hex[3].startsWith(hex[3].charAt(1))
) {
return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
}
return hex.join('');
}
/**
* Converts an RGBA color to an ARGB Hex8 string
* 将RGBA颜色转换为ARGB十六进制字符串。
* Rarely used, but required for "toFilter()"
*/
export function rgbaToArgbHex(r : number, g : number, b : number, a : number) : string {
const hex = [
pad2(convertDecimalToHex(a)),
pad2(Math.round(r).toString(16)),
pad2(Math.round(g).toString(16)),
pad2(Math.round(b).toString(16)),
];
return hex.join('');
}
/** Converts a decimal to a hex value */
/** 将十进制转换为十六进制值。 */
function convertDecimalToHex(d : number) : string
function convertDecimalToHex(d : string) : string
function convertDecimalToHex(d : any) : string {
return Math.round(parseFloat(`${d}`) * 255).toString(16);
}
export {convertDecimalToHex}
/** Converts a hex value to a decimal */
export function convertHexToDecimal(h : string) : number {
return parseIntFromHex(h) / 255;
}
/** Parse a base-16 hex value into a base-10 integer */
export function parseIntFromHex(val : string) : number {
return parseInt(val, 16);
}
export function numberInputToObject(color : number) : RGB {
return {
r: color >> 16,
g: (color & 0xff00) >> 8,
b: color & 0xff,
} as RGB;
}

View File

@@ -0,0 +1,155 @@
// @ts-nocheck
// https://github.com/bahamas10/css-color-names/blob/master/css-color-names.json
/**
* @hidden
*/
export const names: Map<string, string> = new Map<string, string>([
['aliceblue', '#f0f8ff'],
['antiquewhite', '#faebd7'],
['aqua', '#00ffff'],
['aquamarine', '#7fffd4'],
['azure', '#f0ffff'],
['beige', '#f5f5dc'],
['bisque', '#ffe4c4'],
['black', '#000000'],
['blanchedalmond', '#ffebcd'],
['blue', '#0000ff'],
['blueviolet', '#8a2be2'],
['brown', '#a52a2a'],
['burlywood', '#deb887'],
['cadetblue', '#5f9ea0'],
['chartreuse', '#7fff00'],
['chocolate', '#d2691e'],
['coral', '#ff7f50'],
['cornflowerblue', '#6495ed'],
['cornsilk', '#fff8dc'],
['crimson', '#dc143c'],
['cyan', '#00ffff'],
['darkblue', '#00008b'],
['darkcyan', '#008b8b'],
['darkgoldenrod', '#b8860b'],
['darkgray', '#a9a9a9'],
['darkgreen', '#006400'],
['darkgrey', '#a9a9a9'],
['darkkhaki', '#bdb76b'],
['darkmagenta', '#8b008b'],
['darkolivegreen', '#556b2f'],
['darkorange', '#ff8c00'],
['darkorchid', '#9932cc'],
['darkred', '#8b0000'],
['darksalmon', '#e9967a'],
['darkseagreen', '#8fbc8f'],
['darkslateblue', '#483d8b'],
['darkslategray', '#2f4f4f'],
['darkslategrey', '#2f4f4f'],
['darkturquoise', '#00ced1'],
['darkviolet', '#9400d3'],
['deeppink', '#ff1493'],
['deepskyblue', '#00bfff'],
['dimgray', '#696969'],
['dimgrey', '#696969'],
['dodgerblue', '#1e90ff'],
['firebrick', '#b22222'],
['floralwhite', '#fffaf0'],
['forestgreen', '#228b22'],
['fuchsia', '#ff00ff'],
['gainsboro', '#dcdcdc'],
['ghostwhite', '#f8f8ff'],
['goldenrod', '#daa520'],
['gold', '#ffd700'],
['gray', '#808080'],
['green', '#008000'],
['greenyellow', '#adff2f'],
['grey', '#808080'],
['honeydew', '#f0fff0'],
['hotpink', '#ff69b4'],
['indianred', '#cd5c5c'],
['indigo', '#4b0082'],
['ivory', '#fffff0'],
['khaki', '#f0e68c'],
['lavenderblush', '#fff0f5'],
['lavender', '#e6e6fa'],
['lawngreen', '#7cfc00'],
['lemonchiffon', '#fffacd'],
['lightblue', '#add8e6'],
['lightcoral', '#f08080'],
['lightcyan', '#e0ffff'],
['lightgoldenrodyellow', '#fafad2'],
['lightgray', '#d3d3d3'],
['lightgreen', '#90ee90'],
['lightgrey', '#d3d3d3'],
['lightpink', '#ffb6c1'],
['lightsalmon', '#ffa07a'],
['lightseagreen', '#20b2aa'],
['lightskyblue', '#87cefa'],
['lightslategray', '#778899'],
['lightslategrey', '#778899'],
['lightsteelblue', '#b0c4de'],
['lightyellow', '#ffffe0'],
['lime', '#00ff00'],
['limegreen', '#32cd32'],
['linen', '#faf0e6'],
['magenta', '#ff00ff'],
['maroon', '#800000'],
['mediumaquamarine', '#66cdaa'],
['mediumblue', '#0000cd'],
['mediumorchid', '#ba55d3'],
['mediumpurple', '#9370db'],
['mediumseagreen', '#3cb371'],
['mediumslateblue', '#7b68ee'],
['mediumspringgreen', '#00fa9a'],
['mediumturquoise', '#48d1cc'],
['mediumvioletred', '#c71585'],
['midnightblue', '#191970'],
['mintcream', '#f5fffa'],
['mistyrose', '#ffe4e1'],
['moccasin', '#ffe4b5'],
['navajowhite', '#ffdead'],
['navy', '#000080'],
['oldlace', '#fdf5e6'],
['olive', '#808000'],
['olivedrab', '#6b8e23'],
['orange', '#ffa500'],
['orangered', '#ff4500'],
['orchid', '#da70d6'],
['palegoldenrod', '#eee8aa'],
['palegreen', '#98fb98'],
['paleturquoise', '#afeeee'],
['palevioletred', '#db7093'],
['papayawhip', '#ffefd5'],
['peachpuff', '#ffdab9'],
['peru', '#cd853f'],
['pink', '#ffc0cb'],
['plum', '#dda0dd'],
['powderblue', '#b0e0e6'],
['purple', '#800080'],
['rebeccapurple', '#663399'],
['red', '#ff0000'],
['rosybrown', '#bc8f8f'],
['royalblue', '#4169e1'],
['saddlebrown', '#8b4513'],
['salmon', '#fa8072'],
['sandybrown', '#f4a460'],
['seagreen', '#2e8b57'],
['seashell', '#fff5ee'],
['sienna', '#a0522d'],
['silver', '#c0c0c0'],
['skyblue', '#87ceeb'],
['slateblue', '#6a5acd'],
['slategray', '#708090'],
['slategrey', '#708090'],
['snow', '#fffafa'],
['springgreen', '#00ff7f'],
['steelblue', '#4682b4'],
['tan', '#d2b48c'],
['teal', '#008080'],
['thistle', '#d8bfd8'],
['tomato', '#ff6347'],
['turquoise', '#40e0d0'],
['violet', '#ee82ee'],
['wheat', '#f5deb3'],
['white', '#ffffff'],
['whitesmoke', '#f5f5f5'],
['yellow', '#ffff00'],
['yellowgreen', '#9acd32'],
]);

View File

@@ -0,0 +1,356 @@
// @ts-nocheck
import { HSL, HSLA, HSV, HSVA, HSB, HSBA,RGB, RGBA, LColorInfo, LColorFormats } from '../utssdk/interface.uts';
import { convertHexToDecimal, hslToRgb, hsvToRgb, parseIntFromHex, rgbToRgb } from './conversion';
import { names } from './css-color-names';
import { boundAlpha, convertToPercentage, toBoolean } from './util';
type ColorMatchers = {
CSS_UNIT : RegExp,
rgb : RegExp,
rgba : RegExp,
hsl : RegExp,
hsla : RegExp,
hsv : RegExp,
hsva : RegExp,
hsb : RegExp,
hsba : RegExp,
hex3 : RegExp,
hex6 : RegExp,
hex4 : RegExp,
hex8 : RegExp,
}
// #ifndef UNI-APP-X
type UTSJSONObject = object
// #endif
// <http://www.w3.org/TR/css3-values/#integers>
const CSS_INTEGER = '[-\\+]?\\d+%?';
// <http://www.w3.org/TR/css3-values/#number-value>
const CSS_NUMBER = '[-\\+]?\\d*\\.\\d+%?';
// Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
// 允许正负整数/数字。不要捕获要么/或者,只需捕获整个结果。
const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})`;
// Actual matching.
// Parentheses and commas are optional, but not required.
// Whitespace can take the place of commas or opening paren
// 实际匹配。
// 圆括号和逗号是可选的,但不是必需的。
// 空格可以代替逗号或左括号
const PERMISSIVE_MATCH3 = '[\\s|\\(]+(' +
CSS_UNIT +
')[,|\\s]+(' +
CSS_UNIT +
')[,|\\s]+(' +
CSS_UNIT +
')\\s*\\)?';;
// const PERMISSIVE_MATCH3 = `[\\s|\\(]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})\\s*\\)?`;
const PERMISSIVE_MATCH4 = '[\\s|\\(]+(' +
CSS_UNIT +
')[,|\\s]+(' +
CSS_UNIT +
')[,|\\s]+(' +
CSS_UNIT +
')[,|\\s]+(' +
CSS_UNIT +
')\\s*\\)?';
// const PERMISSIVE_MATCH4 = `[\\s|\\(]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})\\s*\\)?`;
export const matchers = {
CSS_UNIT: new RegExp(CSS_UNIT),
rgb: new RegExp('rgb' + PERMISSIVE_MATCH3),
rgba: new RegExp('rgba' + PERMISSIVE_MATCH4),
hsl: new RegExp('hsl' + PERMISSIVE_MATCH3),
hsla: new RegExp('hsla' + PERMISSIVE_MATCH4),
hsv: new RegExp('hsv' + PERMISSIVE_MATCH3),
hsva: new RegExp('hsva' + PERMISSIVE_MATCH4),
hsb: new RegExp('hsb' + PERMISSIVE_MATCH3),
hsba: new RegExp('hsba' + PERMISSIVE_MATCH4),
hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
} as ColorMatchers;
/**
* Check to see if it looks like a CSS unit
* 检查它是否看起来像一个 CSS 单位
* (see `matchers` above for definition).
*/
function isValidCSSUnit(color : string) : boolean
function isValidCSSUnit(color : number) : boolean
// #ifndef APP
function isValidCSSUnit(color : any|null) : boolean
// #endif
function isValidCSSUnit(color : any|null) : boolean {
return toBoolean(matchers.CSS_UNIT.exec(`${color}`));
}
export { isValidCSSUnit }
function inputToRGB(color : string) : LColorInfo
function inputToRGB(color : RGB) : LColorInfo
function inputToRGB(color : RGBA) : LColorInfo
function inputToRGB(color : HSL) : LColorInfo
function inputToRGB(color : HSLA) : LColorInfo
function inputToRGB(color : HSV) : LColorInfo
function inputToRGB(color : HSVA) : LColorInfo
function inputToRGB(color : HSB) : LColorInfo
function inputToRGB(color : HSBA) : LColorInfo
function inputToRGB(color : any) : LColorInfo {
let _color: UTSJSONObject|null = null
let rgb = { r: 0, g: 0, b: 0 } as RGB;
let a:any = 1;
let s: any | null;
let v: any | null;
let l: any | null;
let ok = false;
let format: LColorFormats | null = null;
if (typeof color == 'string') {
_color = stringInputToObject(color as string);
} else if(typeof color == 'object'){
_color = JSON.parse(JSON.stringify(color)) as UTSJSONObject
} else {
// _color = {} as UTSJSONObject
}
if(_color != null){
if (isValidCSSUnit(_color['r']) && isValidCSSUnit(_color['g']) && isValidCSSUnit(_color['b'])){
rgb = rgbToRgb(_color['r']!, _color['g']!, _color['b']!);
ok = true;
format = `${_color['r']}`.endsWith('%') ? 'prgb' : 'rgb';
} else if(isValidCSSUnit(_color['h']) && isValidCSSUnit(_color['s']) && (isValidCSSUnit(_color['v']) || isValidCSSUnit(_color['b'])) ){
const isHSV = _color['v'] != null
s = convertToPercentage(_color['s']!);
v = isHSV ? convertToPercentage(_color['v']!) : convertToPercentage(_color['b']!);
rgb = hsvToRgb(_color['h']!, s, v);
ok = true;
format = isHSV ? 'hsv' : 'hsb';
} else if(isValidCSSUnit(_color['h']) && isValidCSSUnit(_color['s']) && isValidCSSUnit(_color['l'])){
s = convertToPercentage(_color['s']!);
l = convertToPercentage(_color['l']!);
rgb = hslToRgb(_color['h']!, s, l);
ok = true;
format = 'hsl';
}
if(_color['a']!=null){
a = _color['a']!;
}
}
a = boundAlpha(a);
return {
ok,
format: _color?.['format'] as (string | null) ?? format,
r: Math.min(255, Math.max(rgb.r, 0)),
g: Math.min(255, Math.max(rgb.g, 0)),
b: Math.min(255, Math.max(rgb.b, 0)),
a,
} as LColorInfo
}
export {
inputToRGB
}
/**
* Permissive string parsing. Take in a number of formats, and output an object
* based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}`
*/
export function stringInputToObject(color : string) : UTSJSONObject | null {
let _color = color.trim().toLowerCase();
if (_color.length == 0) {
return null;
}
let named = false;
if (names.get(_color) != null) {
_color = names.get(_color)!;
named = true;
} else if (_color == 'transparent') {
return { r: 0, g: 0, b: 0, a: 0, format: 'name' } as UTSJSONObject;
// return new Map([
// ['r', 0],
// ['g', 0],
// ['b', 0],
// ['a', 0],
// ['format', 'name'],
// ])
}
// Try to match string input using regular expressions.
// Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360]
// Just return an object and let the conversion functions handle that.
// This way the result will be the same whether the tinycolor is initialized with string or object.
let match = matchers.rgb.exec(_color);
if (match != null) {
const r = match[1]
const g = match[2]
const b = match[3]
return { r, g, b } as UTSJSONObject;
// return new Map([
// ['r', match[1]],
// ['g', match[2]],
// ['b', match[3]],
// ])
}
match = matchers.rgba.exec(_color);
if (match != null) {
const r = match[1]
const g = match[2]
const b = match[3]
const a = match[4]
return { r, g, b, a } as UTSJSONObject;
// return new Map([
// ['r', match[1]],
// ['g', match[2]],
// ['b', match[3]],
// ['a', match[4]],
// ])
}
match = matchers.hsl.exec(_color);
if (match != null) {
const h = match[1]
const s = match[2]
const l = match[3]
return { h, s, l } as UTSJSONObject;
// return new Map([
// ['h', match[1]],
// ['s', match[2]],
// ['l', match[3]],
// ])
}
match = matchers.hsla.exec(_color);
if (match != null) {
const h = match[1]
const s = match[2]
const l = match[3]
const a = match[4]
return { h, s, l, a } as UTSJSONObject;
// return new Map([
// ['h', match[1]],
// ['s', match[2]],
// ['l', match[3]],
// ['a', match[4]],
// ])
}
match = matchers.hsv.exec(_color);
if (match != null) {
const h = match[1]
const s = match[2]
const v = match[3]
return { h, s, v } as UTSJSONObject;
// return new Map([
// ['h', match[1]],
// ['s', match[2]],
// ['v', match[3]],
// ])
}
match = matchers.hsva.exec(_color);
if (match != null) {
const h = match[1]
const s = match[2]
const v = match[3]
const a = match[4]
return { h, s, v, a } as UTSJSONObject;
// return new Map([
// ['h', match[1]],
// ['s', match[2]],
// ['v', match[3]],
// ['a', match[4]],
// ])
}
match = matchers.hex8.exec(_color);
if (match != null) {
const r = parseIntFromHex(match[1]!)
const g = parseIntFromHex(match[2]!)
const b = parseIntFromHex(match[3]!)
const a = convertHexToDecimal(match[4]!)
return {
r,
g,
b,
a,
format: named ? 'name' : 'hex8',
} as UTSJSONObject;
// return new Map([
// ['r', parseIntFromHex(match[1])],
// ['g', parseIntFromHex(match[2])],
// ['b', parseIntFromHex(match[3])],
// ['a', convertHexToDecimal(match[4])],
// ['format', named ? 'name' : 'hex8'],
// ])
}
match = matchers.hex6.exec(_color);
if (match != null) {
const r = parseIntFromHex(match[1]!)
const g = parseIntFromHex(match[2]!)
const b = parseIntFromHex(match[3]!)
// const a = parseIntFromHex(match[4])
return {
r,
g,
b,
format: named ? 'name' : 'hex',
} as UTSJSONObject;
// return new Map([
// ['r', parseIntFromHex(match[1])],
// ['g', parseIntFromHex(match[2])],
// ['b', parseIntFromHex(match[3])],
// ['format', named ? 'name' : 'hex'],
// ])
}
match = matchers.hex4.exec(_color);
if (match != null) {
const r = parseIntFromHex((match[1] + match[1]))
const g = parseIntFromHex((match[2] + match[2]))
const b = parseIntFromHex((match[3] + match[3]))
const a = convertHexToDecimal((match[4] + match[4]))
return {
r,
g,
b,
a,
format: named ? 'name' : 'hex8',
} as UTSJSONObject;
// return new Map([
// ['r', parseIntFromHex(match[1] + match[1])],
// ['g', parseIntFromHex(match[2] + match[2])],
// ['b', parseIntFromHex(match[3] + match[3])],
// ['a', convertHexToDecimal(match[4] + match[4])],
// ['format', named ? 'name' : 'hex8'],
// ])
}
match = matchers.hex3.exec(_color);
if (match != null) {
const r = parseIntFromHex((match[1] + match[1]))
const g = parseIntFromHex((match[2] + match[2]))
const b = parseIntFromHex((match[3] + match[3]))
return {
r,
g,
b,
format: named ? 'name' : 'hex',
} as UTSJSONObject;
// return new Map([
// ['r', parseIntFromHex(match[1] + match[1])],
// ['g', parseIntFromHex(match[2] + match[2])],
// ['b', parseIntFromHex(match[3] + match[3])],
// ['format', named ? 'name' : 'hex'],
// ])
}
return null
}

View File

@@ -0,0 +1,202 @@
// https://github.com/ant-design/ant-design-colors/blob/main/src/generate.ts
import { inputToRGB } from './format-input';
import { rgbToHex, rgbToHsv } from './conversion';
import { HSV, LColorInfo, LGenerateOptions} from '../utssdk/interface.uts';
type DarkColorMapItem = {
index : number;
opacity : number;
};
const hueStep = 2; // 色相阶梯
const saturationStep = 0.16; // 饱和度阶梯,浅色部分
const saturationStep2 = 0.05; // 饱和度阶梯,深色部分
const brightnessStep1 = 0.05; // 亮度阶梯,浅色部分
const brightnessStep2 = 0.15; // 亮度阶梯,深色部分
const lightColorCount = 5; // 浅色数量,主色上
const darkColorCount = 4; // 深色数量,主色下
// 暗色主题颜色映射关系表
const darkColorMap = [
{ index: 7, opacity: 0.15 },
{ index: 6, opacity: 0.25 },
{ index: 5, opacity: 0.3 },
{ index: 5, opacity: 0.45 },
{ index: 5, opacity: 0.65 },
{ index: 5, opacity: 0.85 },
{ index: 4, opacity: 0.9 },
{ index: 3, opacity: 0.95 },
{ index: 2, opacity: 0.97 },
{ index: 1, opacity: 0.98 },
] as DarkColorMapItem[];
// 从 TinyColor.toHsv 移植的包装函数
// 保留这里,因为有 `hsv.h * 360`
function toHsv({ r, g, b } : LColorInfo) : HSV {
// 将 RGB 值转换为 HSV 值
const hsv = rgbToHsv(r, g, b);
// 返回一个 HsvObject其中 h 值乘以 360
return { h: hsv.h * 360, s: hsv.s, v: hsv.v } as HSV;
}
// 从 TinyColor.toHexString 移植的包装函数
// 保留这里,因为有前缀 `#`
function toHex({ r, g, b }: LColorInfo) : string {
// 将 RGB 值转换为十六进制字符串,并添加前缀 `#`
return `#${rgbToHex(r, g, b, false)}`;
}
// 从 TinyColor.mix 移植的包装函数,无法进行 tree-shaking
// 数量范围为 [0, 1]
// 假设 color1 和 color2 没有透明度,因为以下源代码也是如此
function mix(rgb1 : LColorInfo, rgb2 : LColorInfo, amount : number) : LColorInfo {
// 将 amount 除以 100得到一个范围为 [0, 1] 的值
const p = amount / 100;
// 计算混合后的 RGB 值
const rgb = {
r: (rgb2.r - rgb1.r) * p + rgb1.r,
g: (rgb2.g - rgb1.g) * p + rgb1.g,
b: (rgb2.b - rgb1.b) * p + rgb1.b,
a: 1
} as LColorInfo;
// 返回混合后的 RGB 对象
return rgb;
}
// 根据给定的 HSV 对象和索引值计算新的色相值
// 如果 light 参数为 true则色相向左转动否则向右转动
function getHue(hsv : HSV, i : number, light : boolean = false) : number {
let hue : number;
// 根据色相不同,色相转向不同
if (Math.round(hsv.h) >= 60 && Math.round(hsv.h) <= 240) {
// 如果色相在 60 到 240 之间,向左转动
hue = light ? Math.round(hsv.h) - hueStep * i : Math.round(hsv.h) + hueStep * i;
} else {
hue = light ? Math.round(hsv.h) + hueStep * i : Math.round(hsv.h) - hueStep * i;
}
if (hue < 0) {
// 如果新的色相值小于 0则加上 360
hue += 360;
} else if (hue >= 360) {
// 如果新的色相值大于等于 360则减去 360
hue -= 360;
}
return hue;
}
// 根据给定的 HSV 对象和索引值计算新的饱和度值
// 如果 light 参数为 true则饱和度减小否则增加
function getSaturation(hsv : HSV, i : number, light : boolean = false) : number {
// grey color don't change saturation
// 如果颜色是灰色(色相和饱和度都为 0则饱和度不变
if (hsv.h == 0 && hsv.s == 0) {
return hsv.s;
}
let saturation : number;
// 如果 light 参数为 true则饱和度减小
if (light) {
saturation = hsv.s - saturationStep * i;
}
// 如果 i 等于 darkColorCount则饱和度增加
else if (i == darkColorCount) {
saturation = hsv.s + saturationStep;
}
// 否则,饱和度增加
else {
saturation = hsv.s + saturationStep2 * i;
}
// 边界值修正
if (saturation > 1) {
saturation = 1;
}
// 第一格的 s 限制在 0.06-0.1 之间
if (light && i == lightColorCount && saturation > 0.1) {
saturation = 0.1;
}
if (saturation < 0.06) {
saturation = 0.06;
}
return parseFloat(saturation.toFixed(2))
}
// 根据给定的 HSV 对象和索引值计算新的亮度值
// 如果 light 参数为 true则亮度增加否则减少
function getValue(hsv : HSV, i : number, light : boolean = false) : number {
let value : number;
// 如果 light 参数为 true则亮度增加
if (light) {
value = hsv.v + brightnessStep1 * i;
} else {
value = hsv.v - brightnessStep2 * i;
}
if (value > 1) {
value = 1;
}
// 返回保留两位小数的亮度值
return parseFloat(value.toFixed(2));
}
/**
* generate 函数用于生成一组基于给定颜色的色彩模式。
* 它可以生成亮色、暗色和深色主题颜色模式。
*
* @param {string} color - 输入的颜色值可以是十六进制、RGB、RGBA、HSL、HSLA或颜色名称。
* @param {LGenerateOptions} [opts] - 可选的生成选项。
* @returns {string[]} - 返回一个包含生成的颜色模式的字符串数组。
*/
export function generate(color : string, opts : LGenerateOptions = {} as LGenerateOptions) : string[] {
const patterns : string[] = [];
const pColor = inputToRGB(color);
// 生成亮色模式
for (let i = lightColorCount; i > 0; i -= 1) {
const hsv = toHsv(pColor);
const colorString : string = toHex(
inputToRGB({
h: getHue(hsv, i, true),
s: getSaturation(hsv, i, true),
v: getValue(hsv, i, true),
}),
);
patterns.push(colorString);
}
// 添加原始颜色
patterns.push(toHex(pColor));
// 生成暗色模式
for (let i = 1; i <= darkColorCount; i += 1) {
const hsv = toHsv(pColor);
const colorString : string = toHex(
inputToRGB({
h: getHue(hsv, i),
s: getSaturation(hsv, i),
v: getValue(hsv, i),
}),
);
patterns.push(colorString);
}
// 如果选项中指定了 dark 主题,则生成深色主题颜色模式
if (opts.theme == 'dark') {
return darkColorMap.map(({ index, opacity }, _):string => {
const darkColorString : string = toHex(
mix(
inputToRGB(opts.backgroundColor ?? '#141414'),
inputToRGB(patterns[index]),
opacity * 100,
),
);
return darkColorString;
});
}
// 返回默认颜色模式
return patterns;
}

View File

@@ -0,0 +1,211 @@
// import * as util from './util-test'
// import * as util from './conversion-test'
// import * as util from './format-input-test'
// import * as util from './color-test'
// import * as util from './generate-test'
import { tinyColor } from './color'
// console.log('generate:', generate('red'))
console.log('tinyColor~~~~~~~~~~~~~~test')
// console.log('示例1',tinyColor('#000'));
// console.log('示例2',tinyColor('000'));
// console.log('示例3',tinyColor('#369C'));
// console.log('示例4',tinyColor('369C'));
// console.log('示例5',tinyColor('#f0f0f6'));
// console.log('示例6',tinyColor('f0f0f6'));
// console.log('示例7',tinyColor('#f0f0f688'));
// console.log(tinyColor('f0f0f688'));
// console.log('示例1',tinyColor('hsl(0, 100%, 50%)').toString());
// console.log('示例2',tinyColor('hsla(0, 100%, 50%, .5)').toString());
// console.log('示例3',tinyColor('hsl(0, 100%, 50%)').toString());
// console.log('示例4',tinyColor('hsl 0 1.0 0.5').toString());
// console.log('示例5',tinyColor({ h: 0, s: 1, l: 0.5 }).toString());
// console.log('示例1',tinyColor('hsv(0, 100%, 100%)').toString());
// console.log('示例2',tinyColor('hsva(0, 100%, 100%, .5)').toString());
// console.log('示例3',tinyColor('hsv (0 100% 100%)').toString());
// console.log('示例4',tinyColor('hsv 0 1 1').toString());
// console.log('示例5',tinyColor({ h: 0, s: 100, v: 100 }).toString());
// console.log('示例1',tinyColor('RED').toString(), tinyColor('RED'));
// console.log('示例2',tinyColor('blanchedalmond').toString());
// console.log('示例3',tinyColor('darkblue').toString());
// console.log('示例1:',0x0,tinyColor(0x0).toString());
// console.log('示例2',tinyColor(0xaabbcc).toString());
// console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
// const color = tinyColor('red')
// const color2 = tinyColor({ r: 255, g: 255, b: 255 })
// console.log('示例1:',color.originalInput);
// console.log('示例2:',color2.originalInput);
// const color3 = tinyColor('red')
// const color4 = tinyColor({ r: 255, g: 255, b: 255 })
// console.log('示例3:',color3.format);
// console.log('示例4:',color4.format);
// const color5 = tinyColor('red');
// console.log(color5.isValid, color5.toHexString())
// const color6 = tinyColor('not a color');
// console.log(color6.isValid, color6.toHexString())
// const color7 = tinyColor('#fff');
// const color8 = tinyColor('#000')
// console.log(color7.getBrightness())
// console.log(color8.getBrightness())
// const color9 = tinyColor('#fff');
// const color10 = tinyColor('#000')
// console.log(color9.isLight())
// console.log(color10.isLight())
// const color11 = tinyColor('#fff');
// const color12 = tinyColor('#000')
// console.log(color11.isDark())
// console.log(color12.isDark())
// const color13 = tinyColor('#fff');
// const color14 = tinyColor('#000')
// console.log(color13.getLuminance())
// console.log(color14.getLuminance())
// const color15 = tinyColor('rgba(255, 0, 0, .5)');
// const color16 = tinyColor('rgb(255, 0, 0)')
// const color17 = tinyColor('transparent')
// console.log(color15.getAlpha())
// console.log(color16.getAlpha())
// console.log(color17.getAlpha())
// const color18 = tinyColor('red');
// console.log(color18.getAlpha()) ; // 1
// color18.setAlpha(0.5);
// console.log(color18.getAlpha()); // .5
// console.log(color18.toRgbString()); // "rgba(255, 0, 0, .5)"
// const color19 = tinyColor('rgba(255, 0, 0, .5)');
// const computedColor = color19.onBackground('rgb(0, 0, 255)');
// console.log('color19',computedColor.toRgbString()); // "rgb(128, 0, 128)"
// const color20 = tinyColor('red');
// console.log('color20',color20.toHsv()); // { h: 0, s: 1, v: 1, a: 1 }
// const color21 = tinyColor('red');
// console.log(color21.toHsvString()); // "hsv(0, 100%, 100%)"
// color21.setAlpha(0.5);
// console.log(color21.toHsvString()); // "hsva(0, 100%, 100%, 0.5)"
// const color33 = tinyColor('red');
// console.log(color33.toHsl()); // { h: 0, s: 1, l: 0.5, a: 1 }
// const color34 = tinyColor('red');
// console.log(color34.toHsvString()); // "hsl(0, 100%, 50%)"
// color34.setAlpha(0.5);
// console.log(color34.toHsvString()); // "hsla(0, 100%, 50%, 0.5)"
// console.log(tinyColor('#aabbcc').toNumber() == 0xaabbcc)
// console.log(tinyColor('rgb(1, 1, 1)').toNumber() == (1 << 16) + (1 << 8) + 1)
// const color35 = tinyColor('red');
// console.log(color35.toHex()); // "ff0000"
// const color36 = tinyColor('red');
// console.log(color36.toHexString()); // "#ff0000"
// const color37 = tinyColor('red');
// console.log(color37.toHex8()); // "ff0000ff"
// const color38 = tinyColor('red');
// console.log(color38.toHex8String()); // "#ff0000ff"
// const color39 = tinyColor('#ff000000');
// console.log(color39.toHexShortString()); // "#ff000000"
// console.log(color39.toHexShortString(true)); // "#f000"
// const color40 = tinyColor('#ff0000ff');
// console.log(color40.toHexShortString()); // "#ff0000"
// console.log(color40.toHexShortString(true)); // "#f00"
// const color41 = tinyColor('red');
// console.log(color41.toRgb()); // { r: 255, g: 0, b: 0, a: 1 }
// const color42 = tinyColor('red');
// console.log(color42.toRgbString()); // "rgb(255, 0, 0)"
// color42.setAlpha(0.5);
// console.log(color42.toRgbString()); // "rgba(255, 0, 0, 0.5)"
// const color43 = tinyColor('red')
// console.log(color43.toPercentageRgb())
// const color44 = tinyColor('red');
// console.log(color44.toPercentageRgbString()); // "rgb(100%, 0%, 0%)"
// color44.setAlpha(0.5);
// console.log(color44.toPercentageRgbString()); // "rgba(100%, 0%, 0%, 0.5)"
// const color45 = tinyColor('red');
// console.log(color45.toName()); // "red"
// const color46 = tinyColor('red');
// console.log(color46.toString()); // "red"
// console.log(color46.toString('hsv')); // "hsv(0, 100%, 100%)"
// const color47 = tinyColor('rgb(255, 0, 0)');
// console.log(color47.toString()); // "rgb(255, 0, 0)"
// color47.setAlpha(0.5);
// console.log(color47.toString()); // "rgba(255, 0, 0, 0.5)"
// const color48 = tinyColor('red')
// .lighten()
// .desaturate()
// .toHexString()
// console.log(color48)
// console.log(tinyColor('#f00').lighten().toString())
// console.log(tinyColor('#f00').lighten(100).toString())
// console.log(tinyColor('#f00').brighten().toString())
console.log('tinyColor',tinyColor({a: 1, h: 0, l: 0.4, s: 1}))
// console.log(tinyColor('#f00').darken(100).toString())
// console.log(tinyColor('#f00').tint().toString())
// console.log(tinyColor('#f00').tint(100).toString())
// console.log(tinyColor('#f00').shade().toString())
// console.log(tinyColor('#f00').shade(100).toString())
// console.log(tinyColor('#f00').desaturate().toString())
// console.log(tinyColor('#f00').desaturate(100).toString())
// console.log(tinyColor('hsl(0, 10%, 50%)').saturate().toString())
// console.log(tinyColor('#f00').greyscale().toString())
// console.log(tinyColor('#f00').spin(180).toString())
// console.log(tinyColor('#f00').spin(-90).toString())
// console.log(tinyColor('#f00').spin(90).toString())
// console.log(tinyColor('#f00').spin(0).toString())
// console.log(tinyColor('#f00').spin(360).toString())
// let color51 = tinyColor('#f0f');
// let color52 = tinyColor('#0f0');
// console.log(color51.mix(color52).toHexString())
// const colors52 = tinyColor('#f00').analogous()
// console.log(colors52.map((t):string => t.toHexString()))
// const colors53 = tinyColor('#f00').monochromatic();
// console.log(colors53.map((t):string => t.toHexString()))
// const colors54 = tinyColor('#f00').splitcomplement();
// console.log(colors54.map((t):string => t.toHexString()));
// const colors55 = tinyColor('#f00').triad();
// console.log(colors55.map((t):string => t.toHexString()));
// const colors56 = tinyColor('#f00').tetrad();
// console.log(colors56.map((t):string => t.toHexString()));
// const colors57 = tinyColor('#f00').polyad(4);
// console.log(colors57.map((t):string => t.toHexString()));
// console.log(tinyColor('#f00').complement().toHexString())
// let color158 = tinyColor('red');
// let color58 = tinyColor('#f00');
// function a():string{
// return 'rgb(255, 0, 0)'
// }
// console.log('tinyColor equals', color158.equals(color58), color158.equals('#f00'), a() == a());

View File

@@ -0,0 +1,200 @@
// import {isNumber} from '@/uni_modules/lime-shared/isNumber'
// import {isString} from '@/uni_modules/lime-shared/isString'
// import {isNumeric} from '@/uni_modules/lime-shared/isNumeric'
// #ifdef APP-ANDROID
import BigDecimal from 'java.math.BigDecimal'
// #endif
export function isNumber(value: any|null):boolean{
// #ifdef APP-ANDROID
return ['Byte', 'UByte','Short','UShort','Int','UInt','Long','ULong','Float','Double','number'].includes(typeof value)
// #endif
// #ifdef APP-IOS
return ['Int8', 'UInt8','Int16','UInt16','Int32','UInt32','Int64','UInt64','Int','UInt','Float','Float16','Float32','Float64','Double', 'number'].includes(typeof value)
// #endif
// #ifndef APP-ANDROID || APP-IOS
return typeof value == 'number' && !isNaN(value);
// #endif
}
export function isString(value: any|null):boolean{
return typeof value == 'string';
}
export function isNumeric(value: any|null):boolean{
if(isNumber(value)) {
return true
} else if(isString(value)) {
// const regex = "-?\\d+(\\.\\d+)?".toRegex()
const regex = new RegExp("^(-)?\\d+(\\.\\d+)?$")
return regex.test(value as string) //regex.matches(value as string)
}
return false
}
export function toBoolean(value: any|null):boolean{
// #ifdef APP
// 根据输入值的类型,返回相应的布尔值
if(isNumber(value)){
return (value as number) != 0;
}
if(isString(value)){
return `${value}`.length > 0;
}
if(typeof value == 'boolean'){
return value as boolean;
}
return value != null
// #endif
// #ifndef APP
return Boolean(value)
// #endif
}
/**
* Check to see if string passed in is a percentage
* 检查传入的字符串是否为百分比
* @hidden
*/
export function isPercentage(n : any) : boolean {
return isString(n) && (n as string).indexOf('%') != -1;
}
/**
* Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
* 需要处理 1.0 为 100%,因为一旦它是数字,它与 1 没有区别
* <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
* @hidden
*/
export function isOnePointZero(n : any) : boolean {
return isString(n) && (n as string).indexOf('.') != -1 && parseFloat(n as string) == 1;
}
/**
* Take input from [0, n] and return it as [0, 1]
* 将输入值从 [0, n] 转换为 [0, 1]
* @hidden
*/
function bound01(n: string, max: number): number
function bound01(n: number, max: number): number
// #ifndef APP
function bound01(n : any, max : number) : number
// #endif
function bound01(n : any, max : number) : number {
if(!(isNumber(n) || isString(n))){
return 1
}
if (isOnePointZero(n)) {
n = '100%';
}
const isPercent = isPercentage(n);
n = (isNumber(n) ? n : parseFloat(n as string)) as number
n = max == 360 ? n : Math.min(max, Math.max(0, n));
// Automatically convert percentage into number
// 自动将百分比转换为数字
if (isPercent) {
n = parseInt(`${Math.min(n, 100) * max}`, 10) / 100;
}
// Handle floating point rounding errors
// 处理浮点数舍入误差
if ( Math.abs(n - max) < 0.000001) {
return 1;
}
// Convert into [0, 1] range if it isn't already
// 如果它还不是,将其转换为 [0, 1] 范围
if (max == 360) {
// If n is a hue given in degrees,
// wrap around out-of-range values into [0, 360] range
// then convert into [0, 1].
// 如果 n 是以度为单位的色调,
// 将超出范围的值环绕到 [0, 360] 范围内
// 然后将其转换为 [0, 1]。
n = (n < 0 ? (n % max) + max : n % max) / max // parseFloat(`${max}`);
} else {
// If n not a hue given in degrees
// Convert into [0, 1] range if it isn't already.
// 如果 n 不是以度为单位的色调
// 如果它还不是,将其转换为 [0, 1] 范围。
n = (n % max) / max //parseFloat(`${max}`);
}
return n;
}
export {bound01}
/**
* Force a number between 0 and 1
* 在 0 和 1 之间强制一个数字
* @hidden
*/
export function clamp01(val : number) : number {
return Math.min(1, Math.max(0, val));
}
/**
* Return a valid alpha value [0,1] with all invalid values being set to 1
* 返回一个有效的 alpha 值 [0,1],将所有无效值设置为 1
* @hidden
*/
function boundAlpha(a: number):number
function boundAlpha(a: string):number
// #ifndef APP
function boundAlpha(a: any|null) : number
// #endif
function boundAlpha(a: any|null) : number {
let n = a == null ? 1 : (isString(a) ? parseFloat(a as string) : a as number)
if (isNaN(n) || n < 0 || n > 1) {
n = 1;
}
return n;
}
export {
boundAlpha
}
/**
* Replace a decimal with it's percentage value
* 用百分比值替换小数
* number | string
* @hidden
*/
function convertToPercentage(n:number):number
function convertToPercentage(n:string):string
// #ifndef APP
function convertToPercentage(n:any): any
// #endif
function convertToPercentage(n:any): any{
// #ifdef APP-ANDROID
n = isNumeric(n) ? parseFloat(typeof n == 'string' ? n as string : BigDecimal.valueOf((n as number).toDouble()).toPlainString()) : n// as number
// #endif
// #ifndef APP-ANDROID
n = isNumeric(n) ? parseFloat(`${n}`) : n// as number
// #endif
if(isNumber(n) && (n as number) <= 1){
return `${n * 100}%`.replace('.0%','%');
}
return n;
}
export {convertToPercentage}
/**
* Force a hex value to have 2 characters
* 强制使十六进制值具有 2 个字符
* @hidden
*/
export function pad2(c : string) : string {
//c.padStart(2, '0');//
return c.length == 1 ? '0' + c : `${c}`;
}