Skip to content

Commit

Permalink
fix(bug): fixes an issue with saturation and lightness where addition…
Browse files Browse the repository at this point in the history
…s/subtractions. Fixes #2
  • Loading branch information
wessberg committed Feb 12, 2019
1 parent a8c90c2 commit 255dcf7
Show file tree
Hide file tree
Showing 13 changed files with 1,256 additions and 1,151 deletions.
61 changes: 61 additions & 0 deletions src/color/color-system/hsl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {HslaTuple} from "./hsla";

export type HslTuple = [number, string, string];

/**
* Converts the given HSL Tuple into a proper HSL color
* @param {HslTuple|HslaTuple} tuple
* @return {string}
*/
export function hslTupleToHsl([H, S, L]: HslTuple | HslaTuple): string {
return `hsl(${H}, ${S}, ${L})`;
}

/**
* Adds lightness to the given HSL(a) tuple and returns a new one
* @param {HslTuple|HslaTuple} tuple
* @param {number} amount
* @return {HslTuple|HslaTuple}
*/
export function addLightnessToHslTuple(tuple: HslTuple | HslaTuple, amount: number): typeof tuple extends HslTuple ? HslTuple : HslaTuple {
const [H, S, inputL, A] = tuple;
const outputL = `${Math.max(0, Math.min(100, parseFloat(inputL) + amount))}%`;
return A != null ? [H, S, outputL, A] : (([H, S, outputL] as unknown) as HslaTuple);
}

/**
* Adds saturation to the given HSL(a) tuple and returns a new one
* @param {HslTuple|HslaTuple} tuple
* @param {number} amount
* @return {HslTuple|HslaTuple}
*/
export function addSaturationToToHslTuple(tuple: HslTuple | HslaTuple, amount: number): typeof tuple extends HslTuple ? HslTuple : HslaTuple {
const [H, inputS, L, A] = tuple;
const outputS = `${Math.max(0, Math.min(100, parseFloat(inputS) + amount))}%`;
return A != null ? [H, outputS, L, A] : (([H, outputS, L] as unknown) as HslaTuple);
}

/**
* Converts a string-represented HSL color to a tuple: [HUE: number, SATURATION: string, LIGHTNESS: string].
* @param {string} hsl - The HSL color to convert.
* @returns {HslTuple} The HSL color in a tuple representation.
* @throws {TypeError} If the first argument is not of type 'string'.
* @throws {TypeError} If the 'hue' couldn't be decoded from the given string.
* @throws {TypeError} If the 'saturation' couldn't be decoded from the given string.
* @throws {TypeError} If the 'lightness' couldn't be decoded from the given string.
*/
export function hslStringToHslTuple(hsl: string): HslTuple {
if (typeof hsl !== "string") throw new TypeError(`first argument to 'hslStringToHslTuple' must be of type 'string'!`);

const result = hsl.slice(hsl.indexOf("(") + 1, hsl.lastIndexOf(")")).split(",");

const HUE = parseInt(result[0]);
const SATURATION = result[1].trim();
const LIGHTNESS = result[2].trim();

if (isNaN(HUE)) throw new TypeError(`Couldn't decode the 'hue' value for the given hsl color: ${hsl}`);
if (isNaN(parseInt(SATURATION))) throw new TypeError(`Couldn't decode the 'saturation' value for the given hsl color: ${hsl}`);
if (isNaN(parseInt(LIGHTNESS))) throw new TypeError(`Couldn't decode the 'lightness' value for the given hsl color: ${hsl}`);

return [HUE, SATURATION.slice(SATURATION.length - 1) !== "%" ? `${SATURATION}%` : SATURATION, LIGHTNESS.slice(LIGHTNESS.length - 1) !== "%" ? `${LIGHTNESS}%` : LIGHTNESS];
}
30 changes: 30 additions & 0 deletions src/color/color-system/hsla.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {hslStringToHslTuple} from "./hsl";

export type HslaTuple = [number, string, string, number];

/**
* Converts the given HSLa Tuple into a proper HSLa color
* @param {HslaTuple} tuple
* @return {string}
*/
export function hslaTupleToHsla([H, S, L, A]: HslaTuple): string {
return `hsla(${H}, ${S}, ${L}, ${A})`;
}

/**
* Converts a string-represented HSLa color to a tuple: [HUE: number, SATURATION: string, LIGHTNESS: string, ALPHA: number].
* @param {string} hsla - The HSLA color to convert.
* @returns {HslaTuple} The HSLA color in a tuple representation.
* @throws {TypeError} If the 'alpha' couldn't be decoded from the given string.
*/
export function hslaStringToHslaTuple(hsla: string): HslaTuple {
if (typeof hsla !== "string") throw new TypeError(`first argument to 'hslaStringToHslaTuple' must be of type 'string'!`);

const [HUE, SATURATION, LIGHTNESS] = hslStringToHslTuple(hsla);
const result = hsla.slice(hsla.indexOf("(") + 1, hsla.lastIndexOf(")")).split(",");
const ALPHA = parseFloat(result[result.length - 1]);

if (isNaN(ALPHA)) throw new TypeError(`Couldn't decode the 'alpha' value for the given hsla color: ${hsla}`);

return [HUE, SATURATION.slice(SATURATION.length - 1) !== "%" ? `${SATURATION}%` : SATURATION, LIGHTNESS.slice(LIGHTNESS.length - 1) !== "%" ? `${LIGHTNESS}%` : LIGHTNESS, ALPHA];
}
36 changes: 36 additions & 0 deletions src/color/color-system/hsv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export type HsvTuple = [number, number, number];

/**
* Converts the given HSV Tuple into a proper HSV color
* @param {HsvTuple} tuple
* @param{"hsb"|"hsv"} hsbOrHsv
* @return {string}
*/
export function hsvTupleToHsv([H, S, V]: HsvTuple, hsbOrHsv: "hsb" | "hsv" = "hsb"): string {
return `${hsbOrHsv}(${H}, ${S}, ${V})`;
}

/**
* Generates a tuple-representation of an HSV/HSB color.
* @param {string} hsvString - An HSV/HSB string. For instance, hsb(5, 10, 20).
* @returns {HsvTuple} A tuple representation of the HSV color.
* @throws {TypeError} If the first argument is not of type 'string'.
* @throws {TypeError} If an HSV color couldn't be extracted from the given string.
*/
export function hsvStringToHsvTuple(hsvString: string): HsvTuple {
if (typeof hsvString !== "string") throw new TypeError(`first argument must be of type 'string'!`);
let hsv = hsvString.match(/hsv\((.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/);
if (hsv == null) hsv = hsvString.match(/hsb\((.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/);

if (hsv == null) throw new TypeError(`Couldn't decode an HSV color from the given input: ${hsvString}`);

const h = parseFloat(hsv[1]);
const s = parseFloat(hsv[2]);
const v = parseFloat(hsv[3]);

if (isNaN(h)) throw new TypeError(`Couldn't decode the 'Hue' value of the given HSV color: '${hsvString}'.`);
if (isNaN(s)) throw new TypeError(`Couldn't decode the 'Saturation' value of the given HSV color: '${hsvString}'.`);
if (isNaN(v)) throw new TypeError(`Couldn't decode the 'Value/Brightness' value of the given HSV color: '${hsvString}'.`);

return [h, s, v];
}
146 changes: 146 additions & 0 deletions src/color/color-system/html-color.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/**
* A map between HTML color names and their hex values.
* @type {Record<string, string>}
*/
export const HTML_COLOR: Record<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",
darkkhaki: "#bdb76b",
darkmagenta: "#8b008b",
darkolivegreen: "#556b2f",
darkorange: "#ff8c00",
darkorchid: "#9932cc",
darkred: "#8b0000",
darksalmon: "#e9967a",
darkseagreen: "#8fbc8f",
darkslateblue: "#483d8b",
darkslategray: "#2f4f4f",
darkturquoise: "#00ced1",
darkviolet: "#9400d3",
deeppink: "#ff1493",
deepskyblue: "#00bfff",
dimgray: "#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",
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",
lightgrey: "#d3d3d3",
lightgreen: "#90ee90",
lightpink: "#ffb6c1",
lightsalmon: "#ffa07a",
lightseagreen: "#20b2aa",
lightskyblue: "#87cefa",
lightslategray: "#778899",
lightsteelblue: "#b0c4de",
lightyellow: "#ffffe0",
lime: "#00ff00",
limegreen: "#32cd32",
linen: "#faf0e6",
magenta: "#ff00ff",
maroon: "#800000",
mediumaquamarine: "#66cdaa",
mediumblue: "#0000cd",
mediumorchid: "#ba55d3",
mediumpurple: "#9370d8",
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: "#d87093",
papayawhip: "#ffefd5",
peachpuff: "#ffdab9",
peru: "#cd853f",
pink: "#ffc0cb",
plum: "#dda0dd",
powderblue: "#b0e0e6",
purple: "#800080",
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",
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"
};
33 changes: 33 additions & 0 deletions src/color/color-system/rgb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
export type RgbTuple = [number, number, number];

/**
* Converts the given RGB Tuple into a proper RGB color
* @param {RgbTuple} tuple
* @return {string}
*/
export function rgbTupleToRgb([R, G, B]: RgbTuple): string {
return `rgb(${R}, ${G}, ${B})`;
}

/**
* Generates a tuple-representation of an RGB color: [RED: number, GREEN: number, BLUE: number].
* @param {string} rgbString - An RGB string. For instance, rgb(RGB_MAX_VALUE, RGB_MAX_VALUE, RGB_MAX_VALUE).
* @returns {RgbTuple} A tuple representation of the RGB color.
* @throws {TypeError} If the first argument is not of type 'string'.
* @throws {TypeError} If an RGB color couldn't be extracted from the given string.
*/
export function rgbStringToRgbTuple(rgbString: string): RgbTuple {
if (typeof rgbString !== "string") throw new TypeError(`first argument must be of type 'string'!`);
const rgb = rgbString.match(/rgb\((.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/);
if (rgb == null) throw new TypeError(`'rgbToHex()' couldn't decode an RGB color from the given input: ${rgbString}`);

const r = parseFloat(rgb[1]);
const g = parseFloat(rgb[2]);
const b = parseFloat(rgb[3]);

if (isNaN(r)) throw new TypeError(`'rgbToString()' couldn't decode the 'red' value of the given RGB color: '${rgbString}'.`);
if (isNaN(g)) throw new TypeError(`'rgbToString()' couldn't decode the 'green' value of the given RGB color: '${rgbString}'.`);
if (isNaN(b)) throw new TypeError(`'rgbToString()' couldn't decode the 'blue' value of the given RGB color: '${rgbString}'.`);

return [r, g, b];
}
37 changes: 37 additions & 0 deletions src/color/color-system/rgba.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {RgbTuple} from "./rgb";

export type RgbaTuple = [number, number, number, number];

/**
* Converts the given RGBa Tuple into a proper RGBa color
* @param {RgbaTuple|RgbTuple} tuple
* @return {string}
*/
export function rgbaTupleToRgba([R, G, B, A]: RgbTuple | RgbaTuple): string {
return `rgba(${R}, ${G}, ${B}, ${A})`;
}

/**
* Generates a tuple-representation of an RGBa color: [RED: number, GREEN: number, BLUE: number, ALPHA: number].
* @param {string} rgbaString - An RGBa string. For instance, rgb(RGB_MAX_VALUE, RGB_MAX_VALUE, RGB_MAX_VALUE, 0.4).
* @returns {RgbaTuple} A tuple representation of the RGBa color.
* @throws {TypeError} If the first argument is not of type 'string'.
* @throws {TypeError} If an RGBa color couldn't be extracted from the given string.
*/
export function rgbaStringToRgbaTuple(rgbaString: string): RgbaTuple {
if (typeof rgbaString !== "string") throw new TypeError(`first argument must be of type 'string'!`);
const rgba = rgbaString.match(/rgba\((.+)\s*,\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/);
if (rgba == null) throw new TypeError(`Couldn't decode an RGBa color from the given input: ${rgbaString}`);

const r = parseFloat(rgba[1]);
const g = parseFloat(rgba[2]);
const b = parseFloat(rgba[3]);
const a = parseFloat(rgba[4]);

if (isNaN(r)) throw new TypeError(`Couldn't decode the 'red' value of the given RGBa color: '${rgbaString}'.`);
if (isNaN(g)) throw new TypeError(`Couldn't decode the 'green' value of the given RGBa color: '${rgbaString}'.`);
if (isNaN(b)) throw new TypeError(`Couldn't decode the 'blue' value of the given RGBa color: '${rgbaString}'.`);
if (isNaN(a)) throw new TypeError(`Couldn't decode the 'alpha' value of the given RGBa color: '${rgbaString}'.`);

return [r, g, b, a];
}
5 changes: 5 additions & 0 deletions src/color/constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* The maximum possible RGB value
* @type {number}
*/
export const RGB_MAX_VALUE: number = 255;
Loading

0 comments on commit 255dcf7

Please sign in to comment.