Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ colorString.get('#FFFFFFAA') // {model: 'rgb', value: [255,
colorString.get('hsl(360, 100%, 50%)') // {model: 'hsl', value: [0, 100, 50, 1]}
colorString.get('hsl(360 100% 50%)') // {model: 'hsl', value: [0, 100, 50, 1]}
colorString.get('hwb(60, 3%, 60%)') // {model: 'hwb', value: [60, 3, 60, 1]}
colorString.get('oklch(50% 50% 250deg)') // {model: 'oklch', value: [0.5, 0.2, 250, 1]}
colorString.get('oklch(0.5 0.2 250 / .5)') // {model: 'oklch', value: [0.5, 0.2, 250, 0.5]}
colorString.get('lab(25 62.5 125)') // {model: 'lab', value: [25, 62.5, 125, 1]}
colorString.get('lab(25% 50% 100% / 50%)') // {model: 'lab', value: [25, 62.5, 125, 0.5]}

colorString.get.rgb('#FFF') // [255, 255, 255, 1]
colorString.get.rgb('blue') // [0, 0, 255, 1]
Expand All @@ -37,6 +41,12 @@ colorString.get.hwb('hwb(60 3% 60%)') // [60, 3, 60, 1]
colorString.get.hwb('hwb(60, 3%, 60%)') // [60, 3, 60, 1]
colorString.get.hwb('hwb(60, 3%, 60%, 0.6)') // [60, 3, 60, 0.6]

colorString.get.oklch('oklch(50% 50% 250deg)') // [0.5, 0.2, 250, 1]
colorString.get.oklch('oklch(0.5 0.2 250 / .5)') // [0.5, 0.2, 250, 0.5]

colorString.get.lab('lab(25 62.5 125)') // [25, 62.5, 125, 1]
colorString.get.lab('lab(25% 50% 100% / 50%)') // [25, 62.5, 125, 0.5]

colorString.get.rgb('invalid color string') // null
```

Expand Down
122 changes: 121 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const cs = {
};

cs.get = function (string) {
const prefix = string.slice(0, 3).toLowerCase();
const prefix = string.slice(0, string.indexOf("(")).toLowerCase();
let value;
let model;
switch (prefix) {
Expand All @@ -31,6 +31,18 @@ cs.get = function (string) {
break;
}

case 'oklch': {
value = cs.get.oklch(string);
model = 'oklch';
break;
}

case 'lab': {
value = cs.get.lab(string);
model = 'lab';
break;
}

default: {
value = cs.get.rgb(string);
model = 'rgb';
Expand Down Expand Up @@ -168,6 +180,87 @@ cs.get.hwb = function (string) {
return null;
};

cs.get.lab = function (string) {
if (!string) {
return null;
}

const lab = /^lab\(\s*(?<L>([0-9]{1,3}(\.\d*)?%?)|(none))\s+(?<a>(-?[0-9]{1,3}(\.\d*)?%?)|(none))\s+(?<b>(-?[0-9]{1,3}(\.\d*)?%?)|(none))(\s+\/\s+(?<A>(([0-9]{1,3}(\.\d+)?|(\.\d+))%?)|(none)))?\)$/;
const match = string.match(lab);

if (match) {
const match_L = match.groups.L;
const match_a = match.groups.a;
const match_b = match.groups.b;
const match_alpha = match.groups.A || 'none';

// a and b are numbers between -125 and 125 or percentages between -100% and 100%
const parseAB = (str) => {
if (str == "none")
return 0; // default value for "none"
if (str.endsWith("%")) {
const percentage = Number.parseFloat(str.slice(0, -1));
return percentage * 1.25;
}
return Number.parseFloat(str);
}

let a = clamp(parseAB(match_a), -125, 125);
let b = clamp(parseAB(match_b), -125, 125);

let L = 0; // default value for "none"
let alpha = 1; // default value for "none"

// L is a number between 0 and 100 or a percentage between 0% and 100%
if (match_L != "none")
L = clamp(match_L.endsWith("%") ? Number.parseFloat(match_L.slice(0, -1)) : Number.parseFloat(match_L), 0, 100);

// Alpha is a number between 0 and 1 or a percentage between 0% and 100%; it is optional and defaults to 1
if (match_alpha.length > 0 && match_alpha != "none")
alpha = clamp(match_alpha.endsWith("%") ? Number.parseFloat(match_alpha.slice(0, -1)) / 100 : Number.parseFloat(match_alpha), 0, 1);

return [L, a, b, alpha];
}

return null;
};

cs.get.oklch = function (string) {
if (!string) {
return null;
}

const oklch = /^oklch\(\s*(?<L>(([0-9]{1,3}(\.\d*)?%)|[0-1]?(\.\d*)?)|(none))\s+(?<C>(-?[0-9]{1,3}(\.\d*)?%?)|(none))\s+(?<H>(-?[0-9]{1,3}(\.\d*)?(deg)?)|(none))(\s+\/\s+(?<A>((([0-9]{1,3}(\.\d+)?)|(\.\d+))%?)|(none)))?\)$/;
const match = string.match(oklch);

if (match) {
const match_L = match.groups.L.replace("none", "0%");
const match_C = match.groups.C.replace("none", "0%");
const match_H = match.groups.H.replace("none", "0");
const match_alpha = (match.groups.A || "100%").replace("none", "100%");

// L (perceived lightness): number between 0 and 1 or percentage between 0% and 100% or "none" (= 0%)
let L = clamp(match_L.endsWith("%") ? Number.parseFloat(match_L.slice(0, -1)) / 100 : Number.parseFloat(match_L), 0, 1);

// C (chroma): number from 0 (no max) or percentage from 0% (0% = 0, 100% = 0.4) or "none" (= 0%)
let C = match_C.endsWith("%") ? Number.parseFloat(match_C.slice(0, -1)) / 250 : Number.parseFloat(match_C);
if (C < 0)
C = 0;

// H (hue): number or angle or "none" (= 0deg), no min/max
let H = Number.parseFloat(match_H.replace("deg", ""));

// A (alpha, optional): number between 0 and 1 or percentage between 0% and 100% or "none" (= 100%)
let A = 1;
if (match_alpha.length)
A = clamp(match_alpha.endsWith("%") ? Number.parseFloat(match_alpha) / 100 : Number.parseFloat(match_alpha), 0, 1);

return [L, C, H, A];
}

return null;
};

cs.to.hex = function (...rgba) {
return (
'#' +
Expand Down Expand Up @@ -213,6 +306,33 @@ cs.to.hwb = function (...hwba) {
return 'hwb(' + hwba[0] + ', ' + hwba[1] + '%, ' + hwba[2] + '%' + a + ')';
};

cs.to.lab = function(...lab) {
let alpha = '';
if (lab.length >= 4 && lab[3] != 1) {
alpha = ' / ' + lab[3];
}

return 'lab(' + lab[0] + ' ' + lab[1] + ' ' + lab[2] + alpha + ')';
}

cs.to.oklch = function(...lch) {
let alpha = '';
if (lch.length >= 4 && lch[3] != 1) {
alpha = ' / ' + lch[3];
}

return 'oklch(' + lch[0] + ' ' + lch[1] + ' ' + lch[2] + alpha + ')';
}

cs.to.oklch.percent = function(...lch) {
let alpha = '';
if (lch.length >= 4 && lch[3] != 1) {
alpha = ' / ' + lch[3] * 100 + '%';
}

return 'oklch(' + lch[0] * 100 + '% ' + lch[1] * 250 + '% ' + lch[2] + 'deg' + alpha + ')';
}

cs.to.keyword = function (...rgb) {
return reverseNames[rgb.slice(0, 3)];
};
Expand Down
50 changes: 50 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ assert.deepEqual(string.get.hsl('hsl(240deg 100% 50.5%)'), [240, 100, 50.5, 1]);
assert.deepEqual(string.get.hwb('hwb(240, 100%, 50.5%)'), [240, 100, 50.5, 1]);
assert.deepEqual(string.get.hwb('hwb(240deg, 100%, 50.5%)'), [240, 100, 50.5, 1]);
assert.deepEqual(string.get.hwb('hwb(12 50% 0%)'), [12, 50, 0, 1]);
assert.deepEqual(string.get.oklch('oklch(50% 50% 250deg)'), [0.5, 0.2, 250, 1]);
assert.deepEqual(string.get.oklch('oklch(0.5 0.2 250)'), [0.5, 0.2, 250, 1]);
assert.deepEqual(string.get.oklch('oklch(none none none)'), [0, 0, 0, 1]);
assert.deepEqual(string.get.lab('lab(25 62.5 125)'), [25, 62.5, 125, 1]);
assert.deepEqual(string.get.lab('lab(25% 50% 100%)'), [25, 62.5, 125, 1]);
assert.deepEqual(string.get.lab('lab(none none none)'), [0, 0, 0, 1]);

// Generic .get()
assert.deepEqual(string.get('invalid'), null);
Expand All @@ -46,6 +52,12 @@ assert.deepEqual(string.get('hsl(240deg 100% 50.5%)'), {model: 'hsl', value: [24
assert.deepEqual(string.get('hwb(240, 100%, 50.5%)'), {model: 'hwb', value: [240, 100, 50.5, 1]});
assert.deepEqual(string.get('hwb(240deg, 100%, 50.5%)'), {model: 'hwb', value: [240, 100, 50.5, 1]});
assert.deepEqual(string.get('hwb(12 50% 0%)'), {model: 'hwb', value: [12, 50, 0, 1]});
assert.deepEqual(string.get('oklch(50% 50% 250deg)'), {model: 'oklch', value: [0.5, 0.2, 250, 1]});
assert.deepEqual(string.get('oklch(0.5 0.2 250)'), {model: 'oklch', value: [0.5, 0.2, 250, 1]});
assert.deepEqual(string.get('oklch(none none none)'), {model: 'oklch', value: [0, 0, 0, 1]});
assert.deepEqual(string.get('lab(25 62.5 125)'), {model: 'lab', value: [25, 62.5, 125, 1]});
assert.deepEqual(string.get('lab(25% 50% 100%)'), {model: 'lab', value: [25, 62.5, 125, 1]});
assert.deepEqual(string.get('lab(none none none)'), {model: 'lab', value: [0, 0, 0, 1]});

// Invalid generic .get() calls
assert.deepEqual(string.get('hsla(250, 100%, 50%, 50%)'), null);
Expand All @@ -68,6 +80,15 @@ assert.strictEqual(string.get('rgb(10%, 2%, 2348723%dskjfs)'), null);
assert.strictEqual(string.get('rgb(10%, 2%, 2348723dskjfs%)'), null);
assert.strictEqual(string.get('rgb(10$,3)'), null);
assert.strictEqual(string.get('rgba(10, 3)'), null);
assert.strictEqual(string.get('oklch(1, 0.2, 300)'), null);
assert.strictEqual(string.get('oklch(1 0.2 300 / )'), null);
assert.strictEqual(string.get('oklch(1 0.2 100%)'), null);
assert.strictEqual(string.get('oklch(1 0.2)'), null);
assert.strictEqual(string.get('oklch(-0.5 -100% -10 / -5)'), null);
assert.strictEqual(string.get('lab(-1 -130 -130)'), null);
assert.strictEqual(string.get('lab(10, 25, 25)'), null);
assert.strictEqual(string.get('lab(10 25 25 / )'), null);
assert.strictEqual(string.get('lab(10 25)'), null);

// With sign
assert.deepEqual(string.get.rgb('rgb(-244, +233, -100)'), [0, 233, 0, 1]);
Expand Down Expand Up @@ -136,6 +157,10 @@ assert.deepEqual(string.get.hsl('hsl(200 20% 33% / 0.2)'), [200, 20, 33, 0.2]);
assert.deepEqual(string.get.hsl('hsl(200 20% 33% / 1e-7)'), [200, 20, 33, 1e-7]);
assert.deepEqual(string.get.hwb('hwb(200, 20%, 33%, 0.2)'), [200, 20, 33, 0.2]);
assert.deepEqual(string.get.hwb('hwb(200, 20%, 33%, 1e-7)'), [200, 20, 33, 1e-7]);
assert.deepEqual(string.get.oklch('oklch(60% 0.4 250 / .5)'), [0.6, 0.4, 250, 0.5]);
assert.deepEqual(string.get.oklch('oklch(0.6 100% 250 / 50%)'), [0.6, 0.4, 250, 0.5]);
assert.deepEqual(string.get.lab('lab(25 62.5 125 / .25)'), [25, 62.5, 125, 0.25]);
assert.deepEqual(string.get.lab('lab(25% 50% 100% / 25%)'), [25, 62.5, 125, 0.25]);

// No alpha
assert.deepEqual(string.get.rgb('#fef'), [255, 238, 255, 1]);
Expand All @@ -149,6 +174,12 @@ assert.deepEqual(string.get.hsl('hsla(0, 0%, 0%, 0)'), [0, 0, 0, 0]);
assert.deepEqual(string.get.hsl('hsl(0 0% 0% / 0)'), [0, 0, 0, 0]);
assert.deepEqual(string.get.hsl('hsl(0deg 0% 0% / 0)'), [0, 0, 0, 0]);
assert.deepEqual(string.get.hwb('hwb(400, 10%, 200%, 0)'), [40, 10, 100, 0]);
assert.deepEqual(string.get.oklch('oklch(60% 0.4 250 / 0)'), [0.6, 0.4, 250, 0]);
assert.deepEqual(string.get.oklch('oklch(0.6 100% 250 / none)'), [0.6, 0.4, 250, 1]);
assert.deepEqual(string.get.oklch('oklch(0.6 100% 250)'), [0.6, 0.4, 250, 1]);
assert.deepEqual(string.get.lab('lab(25 62.5 125 / none)'), [25, 62.5, 125, 1]);
assert.deepEqual(string.get.lab('lab(25 62.5 125 / 0)'), [25, 62.5, 125, 0]);
assert.deepEqual(string.get.lab('lab(25% 50% 100%)'), [25, 62.5, 125, 1]);

// Range
assert.deepEqual(string.get.rgb('rgba(300, 600, 100, 3)'), [255, 255, 100, 1]);
Expand All @@ -158,6 +189,8 @@ assert.deepEqual(string.get.rgb('rgba(8000% 100% 333% / 88)'), [255, 255, 255, 1
assert.deepEqual(string.get.hsl('hsla(400, 10%, 200%, 10)'), [40, 10, 100, 1]);
assert.deepEqual(string.get.hsl('hsl(400 10% 200% / 10)'), [40, 10, 100, 1]);
assert.deepEqual(string.get.hwb('hwb(400, 10%, 200%, 10)'), [40, 10, 100, 1]);
assert.deepEqual(string.get.oklch('oklch(1.2 -100% 400 / 10)'), [1, 0, 400, 1]);
assert.deepEqual(string.get.lab('lab(101 130 130)'), [100, 125, 125, 1]);

// Invalid
assert.strictEqual(string.get.rgb('yellowblue'), null);
Expand Down Expand Up @@ -188,6 +221,15 @@ assert.strictEqual(string.get.hwb('hwb(240, 100%, e'), null);
assert.strictEqual(string.get.hwb('hwb(240, 100%, 0e-'), null);
assert.strictEqual(string.get.hwb('hwb(240, 100%, 0e+'), null);
assert.strictEqual(string.get.hwb('hwb(240, 100%, +000e33'), null);
assert.strictEqual(string.get.oklch('oklch(1, 0.2, 300)'), null);
assert.strictEqual(string.get.oklch('oklch(1 0.2 300 / )'), null);
assert.strictEqual(string.get.oklch('oklch(1 0.2 100%)'), null);
assert.strictEqual(string.get.oklch('oklch(1 0.2)'), null);
assert.strictEqual(string.get.oklch('oklch(-0.5 -100% -10 / -5)'), null);
assert.strictEqual(string.get.lab('lab(-1 -130 -130)'), null);
assert.strictEqual(string.get.lab('lab(10, 25, 25)'), null);
assert.strictEqual(string.get.lab('lab(10 25 25 / )'), null);
assert.strictEqual(string.get.lab('lab(10 25)'), null);

// Generators
assert.equal(string.to.hex(255, 10, 35), '#FF0A23');
Expand All @@ -207,6 +249,14 @@ assert.equal(string.to.hsl(280, 40, 60, 0.3), 'hsla(280, 40%, 60%, 0.3)');
assert.equal(string.to.hwb(280, 40, 60), 'hwb(280, 40%, 60%)');
assert.equal(string.to.hwb(280, 40, 60, 0.3), 'hwb(280, 40%, 60%, 0.3)');

assert.equal(string.to.lab(25, 62.5, 125), 'lab(25 62.5 125)');
assert.equal(string.to.lab(25, 62.5, 125, 0.5), 'lab(25 62.5 125 / 0.5)');

assert.equal(string.to.oklch(0.5, 0.2, 250), 'oklch(0.5 0.2 250)');
assert.equal(string.to.oklch(0.5, 0.2, 250, 0.5), 'oklch(0.5 0.2 250 / 0.5)');
assert.equal(string.to.oklch.percent(0.5, 0.2, 250), 'oklch(50% 50% 250deg)');
assert.equal(string.to.oklch.percent(0.5, 0.2, 250, 0.5), 'oklch(50% 50% 250deg / 50%)');

assert.equal(string.to.keyword(255, 255, 0), 'yellow');
assert.equal(string.to.keyword(100, 255, 0), undefined);

Expand Down