+169
@@ -0,0 +1,169 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import _ from '~/utils';
|
||||
import Type from '~/channels/type';
|
||||
import {TYPE} from '~/constants';
|
||||
import type {RGBA, HSLA, CHANNELS} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
class Channels {
|
||||
|
||||
/* VARIABLES */
|
||||
|
||||
color?: string;
|
||||
changed: boolean;
|
||||
data: CHANNELS; //TSC: It should really be "Partial<CHANNELS>", but TS gets excessively noisy
|
||||
type: Type;
|
||||
|
||||
/* CONSTRUCTOR */
|
||||
|
||||
constructor ( data: RGBA | HSLA | CHANNELS, color?: string ) {
|
||||
|
||||
this.color = color;
|
||||
this.changed = false;
|
||||
this.data = data as CHANNELS; //TSC
|
||||
this.type = new Type ();
|
||||
|
||||
}
|
||||
|
||||
/* API */
|
||||
|
||||
set ( data: RGBA | HSLA | CHANNELS, color?: string ): this {
|
||||
|
||||
this.color = color;
|
||||
this.changed = false;
|
||||
this.data = data as CHANNELS; //TSC
|
||||
this.type.type = TYPE.ALL;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
/* HELPERS */
|
||||
|
||||
_ensureHSL (): void {
|
||||
|
||||
const data = this.data;
|
||||
const {h, s, l} = data;
|
||||
|
||||
if ( h === undefined ) data.h = _.channel.rgb2hsl ( data, 'h' );
|
||||
if ( s === undefined ) data.s = _.channel.rgb2hsl ( data, 's' );
|
||||
if ( l === undefined ) data.l = _.channel.rgb2hsl ( data, 'l' );
|
||||
|
||||
}
|
||||
|
||||
_ensureRGB (): void {
|
||||
|
||||
const data = this.data;
|
||||
const {r, g, b} = data;
|
||||
|
||||
if ( r === undefined ) data.r = _.channel.hsl2rgb ( data, 'r' );
|
||||
if ( g === undefined ) data.g = _.channel.hsl2rgb ( data, 'g' );
|
||||
if ( b === undefined ) data.b = _.channel.hsl2rgb ( data, 'b' );
|
||||
|
||||
}
|
||||
|
||||
/* GETTERS */
|
||||
|
||||
get r (): number {
|
||||
const data = this.data;
|
||||
const r = data.r;
|
||||
if ( !this.type.is ( TYPE.HSL ) && r !== undefined ) return r;
|
||||
this._ensureHSL ();
|
||||
return _.channel.hsl2rgb ( data, 'r' );
|
||||
}
|
||||
|
||||
get g (): number {
|
||||
const data = this.data;
|
||||
const g = data.g;
|
||||
if ( !this.type.is ( TYPE.HSL ) && g !== undefined ) return g;
|
||||
this._ensureHSL ();
|
||||
return _.channel.hsl2rgb ( data, 'g' );
|
||||
}
|
||||
|
||||
get b (): number {
|
||||
const data = this.data;
|
||||
const b = data.b;
|
||||
if ( !this.type.is ( TYPE.HSL ) && b !== undefined ) return b;
|
||||
this._ensureHSL ();
|
||||
return _.channel.hsl2rgb ( data, 'b' );
|
||||
}
|
||||
|
||||
get h (): number {
|
||||
const data = this.data;
|
||||
const h = data.h;
|
||||
if ( !this.type.is ( TYPE.RGB ) && h !== undefined ) return h;
|
||||
this._ensureRGB ();
|
||||
return _.channel.rgb2hsl ( data, 'h' );
|
||||
}
|
||||
|
||||
get s (): number {
|
||||
const data = this.data;
|
||||
const s = data.s;
|
||||
if ( !this.type.is ( TYPE.RGB ) && s !== undefined ) return s;
|
||||
this._ensureRGB ();
|
||||
return _.channel.rgb2hsl ( data, 's' );
|
||||
}
|
||||
|
||||
get l (): number {
|
||||
const data = this.data;
|
||||
const l = data.l;
|
||||
if ( !this.type.is ( TYPE.RGB ) && l !== undefined ) return l;
|
||||
this._ensureRGB ();
|
||||
return _.channel.rgb2hsl ( data, 'l' );
|
||||
}
|
||||
|
||||
get a (): number {
|
||||
return this.data.a;
|
||||
}
|
||||
|
||||
/* SETTERS */
|
||||
|
||||
set r ( r: number ) {
|
||||
this.type.set ( TYPE.RGB );
|
||||
this.changed = true;
|
||||
this.data.r = r;
|
||||
}
|
||||
|
||||
set g ( g: number ) {
|
||||
this.type.set ( TYPE.RGB );
|
||||
this.changed = true;
|
||||
this.data.g = g;
|
||||
}
|
||||
|
||||
set b ( b: number ) {
|
||||
this.type.set ( TYPE.RGB );
|
||||
this.changed = true;
|
||||
this.data.b = b;
|
||||
}
|
||||
|
||||
set h ( h: number ) {
|
||||
this.type.set ( TYPE.HSL );
|
||||
this.changed = true;
|
||||
this.data.h = h;
|
||||
}
|
||||
|
||||
set s ( s: number ) {
|
||||
this.type.set ( TYPE.HSL );
|
||||
this.changed = true;
|
||||
this.data.s = s;
|
||||
}
|
||||
|
||||
set l ( l: number ) {
|
||||
this.type.set ( TYPE.HSL );
|
||||
this.changed = true;
|
||||
this.data.l = l;
|
||||
}
|
||||
|
||||
set a ( a: number ) {
|
||||
this.changed = true;
|
||||
this.data.a = a;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default Channels;
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import Channels from '~/channels';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const channels = new Channels ( { r: 0, g: 0, b: 0, a: 0 }, 'transparent' );
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default channels;
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import {TYPE} from '~/constants';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
class Type {
|
||||
|
||||
/* VARIABLES */
|
||||
|
||||
type: number = TYPE.ALL;
|
||||
|
||||
/* API */
|
||||
|
||||
get (): number {
|
||||
|
||||
return this.type;
|
||||
|
||||
}
|
||||
|
||||
set ( type: number ): void {
|
||||
|
||||
if ( this.type && this.type !== type ) throw new Error ( 'Cannot change both RGB and HSL channels at the same time' );
|
||||
|
||||
this.type = type;
|
||||
|
||||
}
|
||||
|
||||
reset (): void {
|
||||
|
||||
this.type = TYPE.ALL;
|
||||
|
||||
}
|
||||
|
||||
is ( type: number ): boolean {
|
||||
|
||||
return this.type === type;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default Type;
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import _ from '~/utils';
|
||||
import ChannelsReusable from '~/channels/reusable';
|
||||
import {DEC2HEX} from '~/constants';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const Hex = {
|
||||
|
||||
/* VARIABLES */
|
||||
|
||||
re: /^#((?:[a-f0-9]{2}){2,4}|[a-f0-9]{3})$/i,
|
||||
|
||||
/* API */
|
||||
|
||||
parse: ( color: string ): Channels | void => {
|
||||
|
||||
if ( color.charCodeAt ( 0 ) !== 35 ) return; // '#'
|
||||
|
||||
const match = color.match ( Hex.re );
|
||||
|
||||
if ( !match ) return;
|
||||
|
||||
const hex = match[1];
|
||||
const dec = parseInt ( hex, 16 );
|
||||
const length = hex.length;
|
||||
const hasAlpha = length % 4 === 0;
|
||||
const isFullLength = length > 4;
|
||||
const multiplier = isFullLength ? 1 : 17;
|
||||
const bits = isFullLength ? 8 : 4;
|
||||
const bitsOffset = hasAlpha ? 0 : -1;
|
||||
const mask = isFullLength ? 255 : 15;
|
||||
|
||||
return ChannelsReusable.set ({
|
||||
r: ( ( dec >> ( bits * ( bitsOffset + 3 ) ) ) & mask ) * multiplier,
|
||||
g: ( ( dec >> ( bits * ( bitsOffset + 2 ) ) ) & mask ) * multiplier,
|
||||
b: ( ( dec >> ( bits * ( bitsOffset + 1 ) ) ) & mask ) * multiplier,
|
||||
a: hasAlpha ? ( dec & mask ) * multiplier / 255 : 1
|
||||
}, color );
|
||||
|
||||
},
|
||||
|
||||
stringify: ( channels: Channels ): string => {
|
||||
|
||||
const {r, g, b, a} = channels;
|
||||
|
||||
if ( a < 1 ) { // #RRGGBBAA
|
||||
|
||||
return `#${DEC2HEX[Math.round ( r )]}${DEC2HEX[Math.round ( g )]}${DEC2HEX[Math.round ( b )]}${DEC2HEX[Math.round ( a * 255 )]}`;
|
||||
|
||||
} else { // #RRGGBB
|
||||
|
||||
return `#${DEC2HEX[Math.round ( r )]}${DEC2HEX[Math.round ( g )]}${DEC2HEX[Math.round ( b )]}`;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default Hex;
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import _ from '~/utils';
|
||||
import ChannelsReusable from '~/channels/reusable';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const HSL = {
|
||||
|
||||
/* VARIABLES */
|
||||
|
||||
re: /^hsla?\(\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?(?:deg|grad|rad|turn)?)\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?%)\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?%)(?:\s*?(?:,|\/)\s*?\+?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?(%)?))?\s*?\)$/i,
|
||||
hueRe: /^(.+?)(deg|grad|rad|turn)$/i,
|
||||
|
||||
/* HELPERS */
|
||||
|
||||
_hue2deg: ( hue: string ): number => {
|
||||
|
||||
const match = hue.match ( HSL.hueRe );
|
||||
|
||||
if ( match ) {
|
||||
|
||||
const [, number, unit] = match;
|
||||
|
||||
switch ( unit ) {
|
||||
case 'grad': return _.channel.clamp.h ( parseFloat ( number ) * .9 );
|
||||
case 'rad': return _.channel.clamp.h ( parseFloat ( number ) * 180 / Math.PI );
|
||||
case 'turn': return _.channel.clamp.h ( parseFloat ( number ) * 360 );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return _.channel.clamp.h ( parseFloat ( hue ) );
|
||||
|
||||
},
|
||||
|
||||
/* API */
|
||||
|
||||
parse: ( color: string ): Channels | void => {
|
||||
|
||||
const charCode = color.charCodeAt ( 0 );
|
||||
|
||||
if ( charCode !== 104 && charCode !== 72 ) return; // 'h'/'H'
|
||||
|
||||
const match = color.match ( HSL.re );
|
||||
|
||||
if ( !match ) return;
|
||||
|
||||
const [, h, s, l, a, isAlphaPercentage] = match;
|
||||
|
||||
return ChannelsReusable.set ({
|
||||
h: HSL._hue2deg ( h ),
|
||||
s: _.channel.clamp.s ( parseFloat ( s ) ),
|
||||
l: _.channel.clamp.l ( parseFloat ( l ) ),
|
||||
a: a ? _.channel.clamp.a ( isAlphaPercentage ? parseFloat ( a ) / 100 : parseFloat ( a ) ) : 1
|
||||
}, color );
|
||||
|
||||
},
|
||||
|
||||
stringify: ( channels: Channels ): string => {
|
||||
|
||||
const {h, s, l, a} = channels;
|
||||
|
||||
if ( a < 1 ) { // HSLA
|
||||
|
||||
return `hsla(${_.lang.round ( h )}, ${_.lang.round ( s )}%, ${_.lang.round ( l )}%, ${a})`;
|
||||
|
||||
} else { // HSL
|
||||
|
||||
return `hsl(${_.lang.round ( h )}, ${_.lang.round ( s )}%, ${_.lang.round ( l )}%)`;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default HSL;
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import _ from '~/utils';
|
||||
import Hex from '~/color/hex';
|
||||
import HSL from '~/color/hsl';
|
||||
import Keyword from '~/color/keyword';
|
||||
import RGB from '~/color/rgb';
|
||||
import {TYPE} from '~/constants';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const Color = {
|
||||
|
||||
/* VARIABLES */
|
||||
|
||||
format: {
|
||||
keyword: Keyword,
|
||||
hex: Hex,
|
||||
rgb: RGB,
|
||||
rgba: RGB,
|
||||
hsl: HSL,
|
||||
hsla: HSL
|
||||
},
|
||||
|
||||
/* API */
|
||||
|
||||
parse: ( color: string | Channels ): Channels => {
|
||||
|
||||
if ( typeof color !== 'string' ) return color;
|
||||
|
||||
const channels = Hex.parse ( color ) || RGB.parse ( color ) || HSL.parse ( color ) || Keyword.parse ( color ); // Color providers ordered with performance in mind
|
||||
|
||||
if ( channels ) return channels;
|
||||
|
||||
throw new Error ( `Unsupported color format: "${color}"` );
|
||||
|
||||
},
|
||||
|
||||
stringify: ( channels: Channels ): string => {
|
||||
|
||||
// SASS returns a keyword if possible, but we avoid doing that as it's slower and doesn't really add any value
|
||||
|
||||
if ( !channels.changed && channels.color ) return channels.color;
|
||||
|
||||
if ( channels.type.is ( TYPE.HSL ) || channels.data.r === undefined ) {
|
||||
|
||||
return HSL.stringify ( channels );
|
||||
|
||||
} else if ( channels.a < 1 || !Number.isInteger ( channels.r ) || !Number.isInteger ( channels.g ) || !Number.isInteger ( channels.b ) ) {
|
||||
|
||||
return RGB.stringify ( channels );
|
||||
|
||||
} else {
|
||||
|
||||
return Hex.stringify ( channels );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default Color;
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import Hex from '~/color/hex';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const Keyword = {
|
||||
|
||||
/* VARIABLES */
|
||||
|
||||
colors: {
|
||||
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',
|
||||
cyanaqua: '#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',
|
||||
gold: '#ffd700',
|
||||
goldenrod: '#daa520',
|
||||
gray: '#808080',
|
||||
green: '#008000',
|
||||
greenyellow: '#adff2f',
|
||||
grey: '#808080',
|
||||
honeydew: '#f0fff0',
|
||||
hotpink: '#ff69b4',
|
||||
indianred: '#cd5c5c',
|
||||
indigo: '#4b0082',
|
||||
ivory: '#fffff0',
|
||||
khaki: '#f0e68c',
|
||||
lavender: '#e6e6fa',
|
||||
lavenderblush: '#fff0f5',
|
||||
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',
|
||||
tan: '#d2b48c',
|
||||
teal: '#008080',
|
||||
thistle: '#d8bfd8',
|
||||
transparent: '#00000000',
|
||||
turquoise: '#40e0d0',
|
||||
violet: '#ee82ee',
|
||||
wheat: '#f5deb3',
|
||||
white: '#ffffff',
|
||||
whitesmoke: '#f5f5f5',
|
||||
yellow: '#ffff00',
|
||||
yellowgreen: '#9acd32'
|
||||
},
|
||||
|
||||
/* API */
|
||||
|
||||
parse: ( color: string ): Channels | void => {
|
||||
|
||||
color = color.toLowerCase ();
|
||||
|
||||
const hex = Keyword.colors[color];
|
||||
|
||||
if ( !hex ) return;
|
||||
|
||||
return Hex.parse ( hex );
|
||||
|
||||
},
|
||||
|
||||
stringify: ( channels: Channels ): string | undefined => {
|
||||
|
||||
const hex = Hex.stringify ( channels );
|
||||
|
||||
for ( const name in Keyword.colors ) {
|
||||
|
||||
if ( Keyword.colors[name] === hex ) return name;
|
||||
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default Keyword;
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import _ from '~/utils';
|
||||
import ChannelsReusable from '~/channels/reusable';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const RGB = {
|
||||
|
||||
/* VARIABLES */
|
||||
|
||||
re: /^rgba?\(\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?))\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?))\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?))(?:\s*?(?:,|\/)\s*?\+?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?)))?\s*?\)$/i,
|
||||
|
||||
/* API */
|
||||
|
||||
parse: ( color: string ): Channels | void => {
|
||||
|
||||
const charCode = color.charCodeAt ( 0 );
|
||||
|
||||
if ( charCode !== 114 && charCode !== 82 ) return; // 'r'/'R'
|
||||
|
||||
const match = color.match ( RGB.re );
|
||||
|
||||
if ( !match ) return;
|
||||
|
||||
const [, r, isRedPercentage, g, isGreenPercentage, b, isBluePercentage, a, isAlphaPercentage] = match;
|
||||
|
||||
return ChannelsReusable.set ({
|
||||
r: _.channel.clamp.r ( isRedPercentage ? parseFloat ( r ) * 2.55 : parseFloat ( r ) ),
|
||||
g: _.channel.clamp.g ( isGreenPercentage ? parseFloat ( g ) * 2.55 : parseFloat ( g ) ),
|
||||
b: _.channel.clamp.b ( isBluePercentage ? parseFloat ( b ) * 2.55 : parseFloat ( b ) ),
|
||||
a: a ? _.channel.clamp.a ( isAlphaPercentage ? parseFloat ( a ) / 100 : parseFloat ( a ) ) : 1
|
||||
}, color );
|
||||
|
||||
},
|
||||
|
||||
stringify: ( channels: Channels ): string => {
|
||||
|
||||
const {r, g, b, a} = channels;
|
||||
|
||||
if ( a < 1 ) { // RGBA
|
||||
|
||||
return `rgba(${_.lang.round ( r )}, ${_.lang.round ( g )}, ${_.lang.round ( b )}, ${_.lang.round ( a )})`;
|
||||
|
||||
} else { // RGB
|
||||
|
||||
return `rgb(${_.lang.round ( r )}, ${_.lang.round ( g )}, ${_.lang.round ( b )})`;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default RGB;
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import _ from '~/utils';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const DEC2HEX: Record<number, string> = {};
|
||||
|
||||
for ( let i = 0; i <= 255; i++ ) DEC2HEX[i] = _.unit.dec2hex ( i ); // Populating dynamically, striking a balance between code size and performance
|
||||
|
||||
const TYPE = <const> {
|
||||
ALL: 0,
|
||||
RGB: 1,
|
||||
HSL: 2
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export {DEC2HEX, TYPE};
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export * from '~/methods';
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import Color from '~/color';
|
||||
import change from '~/methods/change';
|
||||
import type {CHANNELS, Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const adjust = ( color: string | Channels, channels: Partial<CHANNELS> ): string => {
|
||||
|
||||
const ch = Color.parse ( color );
|
||||
const changes: Partial<CHANNELS> = {};
|
||||
|
||||
for ( const c in channels ) {
|
||||
|
||||
if ( !channels[c] ) continue;
|
||||
|
||||
changes[c] = ch[c] + channels[c];
|
||||
|
||||
}
|
||||
|
||||
return change ( color, changes );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default adjust;
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import _ from '~/utils';
|
||||
import Color from '~/color';
|
||||
import type {CHANNEL, Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const adjustChannel = ( color: string | Channels, channel: CHANNEL, amount: number ): string => {
|
||||
|
||||
const channels = Color.parse ( color );
|
||||
const amountCurrent = channels[channel];
|
||||
const amountNext = _.channel.clamp[channel]( amountCurrent + amount );
|
||||
|
||||
if ( amountCurrent !== amountNext ) channels[channel] = amountNext;
|
||||
|
||||
return Color.stringify ( channels );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default adjustChannel;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import channel from '~/methods/channel';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const alpha = ( color: string | Channels ): number => {
|
||||
|
||||
return channel ( color, 'a' );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default alpha;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import channel from '~/methods/channel';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const blue = ( color: string | Channels ): number => {
|
||||
|
||||
return channel ( color, 'b' );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default blue;
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import _ from '~/utils';
|
||||
import Color from '~/color';
|
||||
import type {CHANNELS, Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const change = ( color: string | Channels, channels: Partial<CHANNELS> ): string => {
|
||||
|
||||
const ch = Color.parse ( color );
|
||||
|
||||
for ( const c in channels ) {
|
||||
|
||||
ch[c] = _.channel.clamp[c]( channels[c] );
|
||||
|
||||
}
|
||||
|
||||
return Color.stringify ( ch );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default change;
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import _ from '~/utils';
|
||||
import Color from '~/color';
|
||||
import type {CHANNEL, Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const channel = ( color: string | Channels, channel: CHANNEL ): number => {
|
||||
|
||||
return _.lang.round ( Color.parse ( color )[channel] );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default channel;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import adjustChannel from '~/methods/adjust_channel';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const complement = ( color: string | Channels ): string => {
|
||||
|
||||
return adjustChannel ( color, 'h', 180 );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default complement;
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import _ from '~/utils';
|
||||
import luminance from '~/methods/luminance';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const contrast = ( color1: string, color2: string ): number => {
|
||||
|
||||
const luminance1 = luminance ( color1 );
|
||||
const luminance2 = luminance ( color2 );
|
||||
const max = Math.max ( luminance1, luminance2 );
|
||||
const min = Math.min ( luminance1, luminance2 );
|
||||
const ratio = ( max + Number.EPSILON ) / ( min + Number.EPSILON );
|
||||
|
||||
return _.lang.round ( _.lang.clamp ( ratio, 1, 10 ) );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default contrast;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import adjustChannel from '~/methods/adjust_channel';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const darken = ( color: string | Channels, amount: number ): string => {
|
||||
|
||||
return adjustChannel ( color, 'l', -amount );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default darken;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import adjustChannel from '~/methods/adjust_channel';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const desaturate = ( color: string | Channels, amount: number ): string => {
|
||||
|
||||
return adjustChannel ( color, 's', -amount );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default desaturate;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import change from '~/methods/change';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const grayscale = ( color: string | Channels ): string => {
|
||||
|
||||
return change ( color, { s: 0 } );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default grayscale;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import channel from '~/methods/channel';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const green = ( color: string | Channels ): number => {
|
||||
|
||||
return channel ( color, 'g' );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default green;
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import _ from '~/utils';
|
||||
import ChannelsReusable from '~/channels/reusable';
|
||||
import Color from '~/color';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const hsla = ( h: number, s: number, l: number, a: number = 1 ): string => {
|
||||
|
||||
const channels = ChannelsReusable.set ({
|
||||
h: _.channel.clamp.h ( h ),
|
||||
s: _.channel.clamp.s ( s ),
|
||||
l: _.channel.clamp.l ( l ),
|
||||
a: _.channel.clamp.a ( a )
|
||||
});
|
||||
|
||||
return Color.stringify ( channels );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default hsla;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import channel from '~/methods/channel';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const hue = ( color: string | Channels ): number => {
|
||||
|
||||
return channel ( color, 'h' );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default hue;
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import hex from '~/methods/rgba'; // Alias
|
||||
import rgb from '~/methods/rgba'; // Alias
|
||||
import rgba from '~/methods/rgba';
|
||||
import hsl from '~/methods/hsla'; // Alias
|
||||
import hsla from '~/methods/hsla';
|
||||
import toKeyword from '~/methods/to_keyword';
|
||||
import toHex from '~/methods/to_hex';
|
||||
import toRgba from '~/methods/to_rgba';
|
||||
import toHsla from '~/methods/to_hsla';
|
||||
import channel from '~/methods/channel';
|
||||
import red from '~/methods/red';
|
||||
import green from '~/methods/green';
|
||||
import blue from '~/methods/blue';
|
||||
import hue from '~/methods/hue';
|
||||
import saturation from '~/methods/saturation';
|
||||
import lightness from '~/methods/lightness';
|
||||
import alpha from '~/methods/alpha';
|
||||
import opacity from '~/methods/alpha'; // Alias
|
||||
import contrast from '~/methods/contrast';
|
||||
import luminance from '~/methods/luminance';
|
||||
import isDark from '~/methods/is_dark';
|
||||
import isLight from '~/methods/is_light';
|
||||
import isTransparent from '~/methods/is_transparent';
|
||||
import isValid from '~/methods/is_valid';
|
||||
import saturate from '~/methods/saturate';
|
||||
import desaturate from '~/methods/desaturate';
|
||||
import lighten from '~/methods/lighten';
|
||||
import darken from '~/methods/darken';
|
||||
import opacify from '~/methods/opacify';
|
||||
import fadeIn from '~/methods/opacify'; // Alias
|
||||
import transparentize from '~/methods/transparentize';
|
||||
import fadeOut from '~/methods/transparentize'; // Alias
|
||||
import complement from '~/methods/complement';
|
||||
import grayscale from '~/methods/grayscale';
|
||||
import adjust from '~/methods/adjust';
|
||||
import change from '~/methods/change';
|
||||
import invert from '~/methods/invert';
|
||||
import mix from '~/methods/mix';
|
||||
import scale from '~/methods/scale';
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export {
|
||||
/* CREATE */
|
||||
hex,
|
||||
rgb,
|
||||
rgba,
|
||||
hsl,
|
||||
hsla,
|
||||
/* CONVERT */
|
||||
toKeyword,
|
||||
toHex,
|
||||
toRgba,
|
||||
toHsla,
|
||||
/* GET - CHANNEL */
|
||||
channel,
|
||||
red,
|
||||
green,
|
||||
blue,
|
||||
hue,
|
||||
saturation,
|
||||
lightness,
|
||||
alpha,
|
||||
opacity,
|
||||
/* GET - MORE */
|
||||
contrast,
|
||||
luminance,
|
||||
isDark,
|
||||
isLight,
|
||||
isTransparent,
|
||||
isValid,
|
||||
/* EDIT - CHANNEL */
|
||||
saturate,
|
||||
desaturate,
|
||||
lighten,
|
||||
darken,
|
||||
opacify,
|
||||
fadeIn,
|
||||
transparentize,
|
||||
fadeOut,
|
||||
complement,
|
||||
grayscale,
|
||||
/* EDIT - MORE */
|
||||
adjust,
|
||||
change,
|
||||
invert,
|
||||
mix,
|
||||
scale
|
||||
};
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import Color from '~/color';
|
||||
import mix from '~/methods/mix';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const invert = ( color: string | Channels, weight: number = 100 ): string => {
|
||||
|
||||
const inverse = Color.parse ( color );
|
||||
|
||||
inverse.r = 255 - inverse.r;
|
||||
inverse.g = 255 - inverse.g;
|
||||
inverse.b = 255 - inverse.b;
|
||||
|
||||
return mix ( inverse, color, weight );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default invert;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import isLight from '~/methods/is_light';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const isDark = ( color: string | Channels ): boolean => {
|
||||
|
||||
return !isLight ( color );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default isDark;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import luminance from '~/methods/luminance';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const isLight = ( color: string | Channels ): boolean => {
|
||||
|
||||
return luminance ( color ) >= .5;
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default isLight;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import alpha from '~/methods/alpha';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const isTransparent = ( color: string | Channels ): boolean => {
|
||||
|
||||
return !alpha ( color );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default isTransparent;
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import Color from '~/color';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const isValid = ( color: string ): boolean => {
|
||||
|
||||
try {
|
||||
|
||||
Color.parse ( color );
|
||||
|
||||
return true;
|
||||
|
||||
} catch {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default isValid;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import adjustChannel from '~/methods/adjust_channel';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const lighten = ( color: string | Channels, amount: number ): string => {
|
||||
|
||||
return adjustChannel ( color, 'l', amount );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default lighten;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import channel from '~/methods/channel';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const lightness = ( color: string | Channels ): number => {
|
||||
|
||||
return channel ( color, 'l' );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default lightness;
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import _ from '~/utils';
|
||||
import Color from '~/color';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
//SOURCE: https://planetcalc.com/7779
|
||||
|
||||
const luminance = ( color: string | Channels ): number => {
|
||||
|
||||
const {r, g, b} = Color.parse ( color );
|
||||
const luminance = .2126 * _.channel.toLinear ( r ) + .7152 * _.channel.toLinear ( g ) + .0722 * _.channel.toLinear ( b );
|
||||
|
||||
return _.lang.round ( luminance );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default luminance;
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import Color from '~/color';
|
||||
import rgba from '~/methods/rgba';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
//SOURCE: https://github.com/sass/dart-sass/blob/7457d2e9e7e623d9844ffd037a070cf32d39c348/lib/src/functions/color.dart#L718-L756
|
||||
|
||||
const mix = ( color1: string | Channels, color2: string | Channels, weight: number = 50 ): string => {
|
||||
|
||||
const {r: r1, g: g1, b: b1, a: a1} = Color.parse ( color1 );
|
||||
const {r: r2, g: g2, b: b2, a: a2} = Color.parse ( color2 );
|
||||
const weightScale = weight / 100;
|
||||
const weightNormalized = ( weightScale * 2 ) - 1;
|
||||
const alphaDelta = a1 - a2;
|
||||
const weight1combined = ( ( weightNormalized * alphaDelta ) === -1 ) ? weightNormalized : ( weightNormalized + alphaDelta ) / ( 1 + weightNormalized * alphaDelta );
|
||||
const weight1 = ( weight1combined + 1 ) / 2;
|
||||
const weight2 = 1 - weight1;
|
||||
const r = ( r1 * weight1 ) + ( r2 * weight2 );
|
||||
const g = ( g1 * weight1 ) + ( g2 * weight2 );
|
||||
const b = ( b1 * weight1 ) + ( b2 * weight2 );
|
||||
const a = ( a1 * weightScale ) + ( a2 * ( 1 - weightScale ) );
|
||||
|
||||
return rgba ( r, g, b, a );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default mix;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import adjustChannel from '~/methods/adjust_channel';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const opacify = ( color: string | Channels, amount: number ): string => {
|
||||
|
||||
return adjustChannel ( color, 'a', amount );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default opacify;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import channel from '~/methods/channel';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const red = ( color: string | Channels ): number => {
|
||||
|
||||
return channel ( color, 'r' );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default red;
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import _ from '~/utils';
|
||||
import ChannelsReusable from '~/channels/reusable';
|
||||
import Color from '~/color';
|
||||
import change from '~/methods/change';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* TYPES */
|
||||
|
||||
type IRgba = {
|
||||
( color: string | Channels, opacity: number ): string,
|
||||
( r: number, g: number, b: number, a?: number ): string
|
||||
};
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const rgba: IRgba = ( r: string | Channels | number, g: number, b: number = 0, a: number = 1 ): string => { //TSC: `b` shouldn't have a default value
|
||||
|
||||
if ( typeof r !== 'number' ) return change ( r, { a: g } );
|
||||
|
||||
const channels = ChannelsReusable.set ({
|
||||
r: _.channel.clamp.r ( r ),
|
||||
g: _.channel.clamp.g ( g ),
|
||||
b: _.channel.clamp.b ( b ),
|
||||
a: _.channel.clamp.a ( a )
|
||||
});
|
||||
|
||||
return Color.stringify ( channels );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default rgba;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import adjustChannel from '~/methods/adjust_channel';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const saturate = ( color: string | Channels, amount: number ): string => {
|
||||
|
||||
return adjustChannel ( color, 's', amount );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default saturate;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import channel from '~/methods/channel';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const saturation = ( color: string | Channels ): number => {
|
||||
|
||||
return channel ( color, 's' );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default saturation;
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import _ from '~/utils';
|
||||
import Color from '~/color';
|
||||
import adjust from '~/methods/adjust';
|
||||
import type {CHANNELS, Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const scale = ( color: string | Channels, channels: Partial<CHANNELS> ): string => {
|
||||
|
||||
const ch = Color.parse ( color );
|
||||
const adjustments: Partial<CHANNELS> = {};
|
||||
const delta = ( amount: number, weight: number, max: number ) => weight > 0 ? ( max - amount ) * weight / 100 : amount * weight / 100;
|
||||
|
||||
for ( const c in channels ) {
|
||||
|
||||
adjustments[c] = delta ( ch[c], channels[c], _.channel.max[c] );
|
||||
|
||||
}
|
||||
|
||||
return adjust ( color, adjustments );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default scale;
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import Color from '~/color';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const toHex = ( color: string ): string => {
|
||||
|
||||
return Color.format.hex.stringify ( Color.parse ( color ) );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default toHex;
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import Color from '~/color';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const toHsla = ( color: string ): string => {
|
||||
|
||||
return Color.format.hsla.stringify ( Color.parse ( color ) );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default toHsla;
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import Color from '~/color';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const toKeyword = ( color: string ): string | undefined => {
|
||||
|
||||
return Color.format.keyword.stringify ( Color.parse ( color ) );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default toKeyword;
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import Color from '~/color';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const toRgba = ( color: string ): string => {
|
||||
|
||||
return Color.format.rgba.stringify ( Color.parse ( color ) );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default toRgba;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import adjustChannel from '~/methods/adjust_channel';
|
||||
import type {Channels} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const transparentize = ( color: string | Channels, amount: number ): string => {
|
||||
|
||||
return adjustChannel ( color, 'a', -amount );
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default transparentize;
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import type Channels from './channels';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
type ALPHA = {
|
||||
a: number // Alpha (0~1)
|
||||
};
|
||||
|
||||
type RGB = {
|
||||
r: number, // Red (0~255)
|
||||
g: number, // Green (0~255)
|
||||
b: number // Blue (0~255)
|
||||
};
|
||||
|
||||
type RGBA = RGB & ALPHA;
|
||||
|
||||
type HSL = {
|
||||
h: number, // Hue (0~360)
|
||||
s: number, // Saturation (0~100)
|
||||
l: number // Lightness (0~100)
|
||||
};
|
||||
|
||||
type HSLA = HSL & ALPHA;
|
||||
|
||||
type CHANNEL = 'r' | 'g' | 'b' | 'h' | 's' | 'l' | 'a';
|
||||
|
||||
type CHANNELS = Record<CHANNEL, number>;
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export type {Channels};
|
||||
export type {ALPHA, RGB, RGBA, HSL, HSLA, CHANNEL, CHANNELS};
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import type {RGB, HSL} from '~/types';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const Channel = {
|
||||
|
||||
/* CLAMP */
|
||||
|
||||
min: {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
s: 0,
|
||||
l: 0,
|
||||
a: 0
|
||||
},
|
||||
|
||||
max: {
|
||||
r: 255,
|
||||
g: 255,
|
||||
b: 255,
|
||||
h: 360,
|
||||
s: 100,
|
||||
l: 100,
|
||||
a: 1
|
||||
},
|
||||
|
||||
clamp: {
|
||||
r: ( r: number ) => r >= 255 ? 255 : ( r < 0 ? 0 : r ),
|
||||
g: ( g: number ) => g >= 255 ? 255 : ( g < 0 ? 0 : g ),
|
||||
b: ( b: number ) => b >= 255 ? 255 : ( b < 0 ? 0 : b ),
|
||||
h: ( h: number ) => h % 360,
|
||||
s: ( s: number ) => s >= 100 ? 100 : ( s < 0 ? 0 : s ),
|
||||
l: ( l: number ) => l >= 100 ? 100 : ( l < 0 ? 0 : l ),
|
||||
a: ( a: number ) => a >= 1 ? 1 : ( a < 0 ? 0 : a )
|
||||
},
|
||||
|
||||
/* CONVERSION */
|
||||
|
||||
//SOURCE: https://planetcalc.com/7779
|
||||
|
||||
toLinear: ( c: number ): number => {
|
||||
|
||||
const n = c / 255;
|
||||
|
||||
return c > .03928 ? Math.pow ( ( ( n + .055 ) / 1.055 ), 2.4 ) : n / 12.92;
|
||||
|
||||
},
|
||||
|
||||
//SOURCE: https://gist.github.com/mjackson/5311256
|
||||
|
||||
hue2rgb: ( p: number, q: number, t: number ): number => {
|
||||
|
||||
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;
|
||||
|
||||
},
|
||||
|
||||
hsl2rgb: ( { h, s, l }: HSL, channel: keyof RGB ): number => {
|
||||
|
||||
if ( !s ) return l * 2.55; // Achromatic
|
||||
|
||||
h /= 360;
|
||||
s /= 100;
|
||||
l /= 100;
|
||||
|
||||
const q = ( l < .5 ) ? l * ( 1 + s ) : ( l + s ) - ( l * s );
|
||||
const p = 2 * l - q;
|
||||
|
||||
switch ( channel ) {
|
||||
case 'r': return Channel.hue2rgb ( p, q, h + 1/3 ) * 255;
|
||||
case 'g': return Channel.hue2rgb ( p, q, h ) * 255;
|
||||
case 'b': return Channel.hue2rgb ( p, q, h - 1/3 ) * 255;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
rgb2hsl: ( { r, g, b }: RGB, channel: keyof HSL ): number => {
|
||||
|
||||
r /= 255;
|
||||
g /= 255;
|
||||
b /= 255;
|
||||
|
||||
const max = Math.max ( r, g, b );
|
||||
const min = Math.min ( r, g, b );
|
||||
const l = ( max + min ) / 2;
|
||||
|
||||
if ( channel === 'l' ) return l * 100;
|
||||
|
||||
if ( max === min ) return 0; // Achromatic
|
||||
|
||||
const d = max - min;
|
||||
const s = ( l > .5 ) ? d / ( 2 - max - min ) : d / ( max + min );
|
||||
|
||||
if ( channel === 's' ) return s * 100;
|
||||
|
||||
switch ( max ) {
|
||||
case r: return ( ( g - b ) / d + ( g < b ? 6 : 0 ) ) * 60;
|
||||
case g: return ( ( b - r ) / d + 2 ) * 60;
|
||||
case b: return ( ( r - g ) / d + 4 ) * 60;
|
||||
default: return -1; //TSC: TypeScript is stupid and complains if there isn't this useless default statement
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default Channel;
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
|
||||
/* IMPORT */
|
||||
|
||||
import channel from '~/utils/channel';
|
||||
import lang from '~/utils/lang';
|
||||
import unit from '~/utils/unit';
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const Utils = {
|
||||
channel,
|
||||
lang,
|
||||
unit
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default Utils;
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const Lang = {
|
||||
|
||||
/* API */
|
||||
|
||||
clamp: ( number: number, lower: number, upper: number ): number => {
|
||||
|
||||
if ( lower > upper ) return Math.min ( lower, Math.max ( upper, number ) );
|
||||
|
||||
return Math.min ( upper, Math.max ( lower, number ) );
|
||||
|
||||
},
|
||||
|
||||
round: ( number: number ): number => { // 10 digits rounding
|
||||
|
||||
return Math.round ( number * 10000000000 ) / 10000000000;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default Lang;
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
|
||||
/* MAIN */
|
||||
|
||||
const Unit = {
|
||||
|
||||
/* API */
|
||||
|
||||
dec2hex: ( dec: number ): string => {
|
||||
|
||||
const hex = Math.round ( dec ).toString ( 16 );
|
||||
|
||||
return hex.length > 1 ? hex : `0${hex}`;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* EXPORT */
|
||||
|
||||
export default Unit;
|
||||
Reference in New Issue
Block a user