zent
Version:
一套前端设计语言和基于React的实现
226 lines (200 loc) • 6.52 kB
text/typescript
import {
convertHexToDecimal,
hslToRgb,
hsvToRgb,
parseIntFromHex,
rgbToRgb,
} from './conversion';
import { names } from './css-color-names';
import { IHSL, IHSLA, IHSV, IHSVA, IRGB, IRGBA } from './interfaces';
import { boundAlpha, convertToPercentage } from './util';
/**
* Given a string or object, convert that input to RGB
*
* Possible string inputs:
* ```
* "red"
* "#f00" or "f00"
* "#ff0000" or "ff0000"
* "#ff000000" or "ff000000"
* "rgb 255 0 0" or "rgb (255, 0, 0)"
* "rgb 1.0 0 0" or "rgb (1, 0, 0)"
* "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
* "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
* "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
* "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
* "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
* ```
*/
export function inputToRGB(
color: string | IRGB | IRGBA | IHSL | IHSLA | IHSV | IHSVA | any
) {
let rgb = { r: 0, g: 0, b: 0 };
let a = 1;
let s: string | number | null = null;
let v: string | number | null = null;
let l: string | number | null = null;
let ok = false;
let format: string | false = false;
if (typeof color === 'string') {
color = stringInputToObject(color);
}
if (typeof color === 'object') {
if (
isValidCSSUnit(color.r) &&
isValidCSSUnit(color.g) &&
isValidCSSUnit(color.b)
) {
rgb = rgbToRgb(color.r, color.g, color.b);
ok = true;
format = String(color.r).substr(-1) === '%' ? 'prgb' : 'rgb';
} else if (
isValidCSSUnit(color.h) &&
isValidCSSUnit(color.s) &&
isValidCSSUnit(color.v)
) {
s = convertToPercentage(color.s);
v = convertToPercentage(color.v);
rgb = hsvToRgb(color.h, s as number, v as number);
ok = true;
format = 'hsv';
} else if (
isValidCSSUnit(color.h) &&
isValidCSSUnit(color.s) &&
isValidCSSUnit(color.l)
) {
s = convertToPercentage(color.s);
l = convertToPercentage(color.l);
rgb = hslToRgb(color.h, s as number, l as number);
ok = true;
format = 'hsl';
}
if (Object.prototype.hasOwnProperty.call(color, 'a')) {
a = color.a;
}
}
a = boundAlpha(a);
return {
ok,
format: color.format || 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,
};
}
// <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_MATCH4 = `[\\s|\\(]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})\\s*\\)?`;
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),
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})$/,
};
/**
* 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}`
*/
function stringInputToObject(color: string): any {
color = color.trim().toLowerCase();
if (color.length === 0) {
return false;
}
let named = false;
if (names[color]) {
color = names[color];
named = true;
} else if (color === 'transparent') {
return { 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) {
return { r: match[1], g: match[2], b: match[3] };
}
match = matchers.rgba.exec(color);
if (match) {
return { r: match[1], g: match[2], b: match[3], a: match[4] };
}
match = matchers.hsl.exec(color);
if (match) {
return { h: match[1], s: match[2], l: match[3] };
}
match = matchers.hsla.exec(color);
if (match) {
return { h: match[1], s: match[2], l: match[3], a: match[4] };
}
match = matchers.hsv.exec(color);
if (match) {
return { h: match[1], s: match[2], v: match[3] };
}
match = matchers.hsva.exec(color);
if (match) {
return { h: match[1], s: match[2], v: match[3], a: match[4] };
}
match = matchers.hex8.exec(color);
if (match) {
return {
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) {
return {
r: parseIntFromHex(match[1]),
g: parseIntFromHex(match[2]),
b: parseIntFromHex(match[3]),
format: named ? 'name' : 'hex',
};
}
match = matchers.hex4.exec(color);
if (match) {
return {
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) {
return {
r: parseIntFromHex(match[1] + match[1]),
g: parseIntFromHex(match[2] + match[2]),
b: parseIntFromHex(match[3] + match[3]),
format: named ? 'name' : 'hex',
};
}
return false;
}
/**
* Check to see if it looks like a CSS unit
* (see `matchers` above for definition).
*/
function isValidCSSUnit(color: string | number) {
return Boolean(matchers.CSS_UNIT.exec(String(color)));
}