// @ts-check
"use strict"
/**
* This functions returns a modulo n.
* @param {number} a
* @param {number} n
* @returns {number}
* @private
*/
const mod = (a, n) => ((a % n) + n) % n;
/**
* This function returns true if alls the values are of the specified type.
* @param {string} type
* @param {...any} values
* @returns {boolean}
* @private
*/
const haveValidType = (type, ...values) => values.every((value) => typeof value === type);
/**
* A set of maximum values for R, G and B.
* @typedef {object} rgbMaximums
* @prop {number} [r=255] The Red maximum value
* @prop {number} [g=255] The Green maximum value
* @prop {number} [b=255] The Blue maximum value
*/
/**
* A set of maximum values for H, S and L.
* @typedef {object} hslMaximums
* @prop {number} [h=360] The Hue maximum value
* @prop {number} [s=100] The Saturation maximum value
* @prop {number} [l=100] The Lightness maximum value
*/
/**
* A set of maximum values for HSL and RGB.
* @typedef {object} hslRgbMaximums
* @prop {hslMaximums} [hsl={ h: 360, s: 100, l: 100 }] The HSL maximum values
* @prop {rgbMaximums} [rgb={ r: 255, g: 255, b:255 }] The RGB maximum values
*/
/**
* A set of HSL values
* @typedef {object} hsl
* @prop {number} h The Hue value
* @prop {number} s The Saturation value
* @prop {number} l The Lightness value
*/
/**
* This function converts an RGB color set into HSL.
* @param {number} r The Red value
* @param {number} g The Green value
* @param {number} b The Blue value
* @param {hslRgbMaximums} [maximums={ rgb: { r: 255, g: 255, b: 255 }, hsl: { h: 360, s: 100, l: 100 } }] The maximum values for RGB (input) and HSL (output)
* @returns {hsl}
*/
export function rgbToHsl(
r,
g,
b,
maximums = {
rgb: {
r: 255,
g: 255,
b: 255
},
hsl: {
h: 360,
s: 100,
l: 100,
}
}
) {
maximums.hsl = maximums.hsl ?? {
h: 360,
s: 100,
l: 100
};
maximums.rgb = maximums.rgb ?? {
r: 255,
g: 255,
b: 255
};
maximums.hsl.h = maximums.hsl.h ?? 360;
maximums.hsl.s = maximums.hsl.s ?? 100;
maximums.hsl.l = maximums.hsl.l ?? 100;
maximums.rgb.r = maximums.rgb.r ?? 255;
maximums.rgb.g = maximums.rgb.g ?? 255;
maximums.rgb.b = maximums.rgb.b ?? 255;
let r_ = r / maximums.rgb.r;
let g_ = g / maximums.rgb.g;
let b_ = b / maximums.rgb.b;
if (!haveValidType("number", r_, g_, b_)) {
return { h: NaN, s: NaN, l: NaN }
}
if (r_ < 0 || r_ > 1 || g_ < 0 || g_ > 1 || b_ < 0 || b_ > 1) {
throw new RangeError("'r', 'g' and 'b' shouldn't be uppon their respective maximum values .");
}
let cMax = Math.max(r_, g_, b_);
let cMin = Math.min(r_, g_, b_);
let delta = cMax - cMin;
let h_, s_, l_;
if (delta == 0) {
h_ = 0;
}
else if (cMax == b_) {
h_ = 60 * (((r_ - g_) / delta) + 4);
}
else if (cMax == g_) {
h_ = 60 * (((b_ - r_) / delta) + 2);
}
else if (cMax == r_) {
h_ = 60 * mod(((g_ - b_) / delta), 6);
}
else {
throw new Error("'cMax' should be 'r_', 'g_' or 'b_'.")
}
if (h_ < 0) {
h_ += 360;
}
l_ = (cMax + cMin) / 2;
if (delta == 0) {
s_ = 0;
}
else {
s_ = delta / (1 - Math.abs(2 * l_ - 1));
}
let result = {
h: (h_ / 360) * maximums.hsl.h,
s: s_ * maximums.hsl.s,
l: l_ * maximums.hsl.l
}
return result
}
/**
* A set of RGB values
* @typedef {object} rgb
* @prop {number} r The Red value
* @prop {number} g The Green value
* @prop {number} b The Blue value
*/
/**
* This function converts an HSL color set into RGB.
* @param {number} h The Hue value
* @param {number} s The Saturation value
* @param {number} l The Lightness value
* @param {hslRgbMaximums} [maximums={ rgb: { r: 255, g: 255, b: 255 }, hsl: { h: 360,s: 100,l: 100 } }] The maximum values for HSL (input) and RGB (output)
* @returns {rgb}
*/
export function hslToRgb(
h,
s,
l,
maximums = {
rgb: {
r: 255,
g: 255,
b: 255
},
hsl: {
h: 360,
s: 100,
l: 100,
}
}
) {
maximums.hsl = maximums.hsl ?? {
h: 360,
s: 100,
l: 100
};
maximums.rgb = maximums.rgb ?? {
r: 255,
g: 255,
b: 255
};
maximums.hsl.h = maximums.hsl.h ?? 360;
maximums.hsl.s = maximums.hsl.s ?? 100;
maximums.hsl.l = maximums.hsl.l ?? 100;
maximums.rgb.r = maximums.rgb.r ?? 255;
maximums.rgb.g = maximums.rgb.g ?? 255;
maximums.rgb.b = maximums.rgb.b ?? 255;
let h_ = h / maximums.hsl.h * 360;
const s_ = s / maximums.hsl.s;
const l_ = l / maximums.hsl.l;
if (!haveValidType("number", h_, s_, l_)) {
return { r: NaN, g: NaN, b: NaN }
}
if (h_ < 0 || h_ > 360 || s_ < 0 || s_ > 1 || l_ < 0 || l_ > 1) {
throw new RangeError("'h', 's' and 'l' shouldn't be uppon their respective maximum values .");
}
else if (h_ == 360) {
h_ = 0;
}
const c = (1 - Math.abs(2 * l_ - 1)) * s_;
const x = c * (1 - Math.abs((h_ / 60) % 2 - 1));
const m = l_ - c / 2;
let r_, g_, b_;
if (h_ < 60) {
r_ = c;
g_ = x;
b_ = 0;
}
else if (h_ < 120) {
r_ = x;
g_ = c;
b_ = 0;
}
else if (h_ < 180) {
r_ = 0;
g_ = c;
b_ = x;
}
else if (h_ < 240) {
r_ = 0;
g_ = x;
b_ = c;
}
else if (h_ < 300) {
r_ = x;
g_ = 0;
b_ = c;
}
else if (h_ < 360) {
r_ = c;
g_ = 0;
b_ = x;
}
else {
throw new Error("'h_' should be below 360.")
}
return {
r: (r_ + m) * maximums.rgb.r,
g: (g_ + m) * maximums.rgb.g,
b: (b_ + m) * maximums.rgb.b
};
}
/**
* A set of maximum values for C, M and Y and K.
* @typedef {object} cmykMaximums
* @prop {number} [c=1] The Cyan maximum value
* @prop {number} [m=1] The Magenta maximum value
* @prop {number} [y=1] The Yellow maximum value
* @prop {number} [k=1] The Key maximum value
*/
/**
* A set of maximum values for CMYK and RGB.
* @typedef {object} cmykRgbMaximums
* @prop {cmykMaximums} [cmyk={ c: 1, m: 1, y: 1, k: 1 }] The CMYK maximum values
* @prop {rgbMaximums} [rgb={ r: 255, g: 255, b: 255 }] The RGB maximum values
*/
/**
* This function converts a CMYK color set into RGB.
* @param {number} c The Cyan value
* @param {number} m The Magenta value
* @param {number} y The Yellow value
* @param {number} k The Key value
* @param {cmykRgbMaximums} [maximums={ rgb: { r: 255, g: 255, b: 255 }, cmyk: { c: 1, m: 1, y: 1, k: 1 } }] The maximum values for CMYK (input) and RGB (output)
* @returns {rgb}
*/
export function cmykToRgb(
c,
m,
y,
k,
maximums = {
rgb: {
r: 255,
g: 255,
b: 255
},
cmyk: {
c: 1,
m: 1,
y: 1,
k: 1
}
}
) {
maximums.cmyk = maximums.cmyk ?? {
c: 255,
m: 255,
y: 255,
k: 255
}
maximums.rgb = maximums.rgb ?? {
r: 255,
g: 255,
b: 255
}
maximums.cmyk.c = maximums.cmyk.c ?? 255
maximums.cmyk.m = maximums.cmyk.m ?? 255
maximums.cmyk.y = maximums.cmyk.y ?? 255
maximums.cmyk.k = maximums.cmyk.k ?? 255
maximums.rgb.r = maximums.rgb.r ?? 255
maximums.rgb.g = maximums.rgb.g ?? 255
maximums.rgb.b = maximums.rgb.b ?? 255
const c_ = c / maximums.cmyk.c
const m_ = m / maximums.cmyk.m
const y_ = y / maximums.cmyk.y
const k_ = k / maximums.cmyk.k
if (!haveValidType("number", c_, m_, y_, k_)) {
return { r: NaN, g: NaN, b: NaN }
}
if (c_ < 0 || c_ > 255 || m_ < 0 || m_ > 255 || y_ < 0 || y_ > 255 || k_ < 0 || k_ > 255) {
throw new RangeError("'c', 'm', 'y' and 'k' shouldn't be uppon their respective maximum values .");
}
const r_ = (1 - c_) * (1 - k_)
const g_ = (1 - m_) * (1 - k_)
const b_ = (1 - y_) * (1 - k_)
const result = {
r: r_ * maximums.rgb.r,
g: g_ * maximums.rgb.g,
b: b_ * maximums.rgb.b
}
return result
}
/**
* A set of values for C, M and Y and K.
* @typedef {object} cmyk
* @prop {number} c The Cyan value
* @prop {number} m The Magenta value
* @prop {number} y The Yellow value
* @prop {number} k The Key value
*/
/**
* This function converts an RGB color set into CMYK.
* @param {number} r The Red value
* @param {number} g The Green value
* @param {number} b The Blue value
* @param {cmykRgbMaximums} [maximums={ rgb: { r:255, g:255, b:255 }, cmyk: { c: 1, m: 1, y: 1,k: 1 } }] The maximum values for RGB (input) and CMYK (output)
* @returns {cmyk}
*/
export function rgbToCmyk(
r,
g,
b,
maximums = {
rgb: {
r: 255,
g: 255,
b: 255
},
cmyk: {
c: 1,
m: 1,
y: 1,
k: 1
}
}
) {
maximums.cmyk = maximums.cmyk ?? {
c: 255,
m: 255,
y: 255,
k: 255
};
maximums.rgb = maximums.rgb ?? {
r: 255,
g: 255,
b: 255
};
maximums.cmyk.c = maximums.cmyk.c ?? 255;
maximums.cmyk.m = maximums.cmyk.m ?? 255;
maximums.cmyk.y = maximums.cmyk.y ?? 255;
maximums.cmyk.k = maximums.cmyk.k ?? 255;
maximums.rgb.r = maximums.rgb.r ?? 255;
maximums.rgb.g = maximums.rgb.g ?? 255;
maximums.rgb.b = maximums.rgb.b ?? 255;
const r_ = r / maximums.rgb.r;
const g_ = g / maximums.rgb.g;
const b_ = b / maximums.rgb.b;
if (!haveValidType("number", r_, g_, b_)) {
return { c: NaN, m: NaN, y: NaN, k: NaN }
}
if (r_ < 0 || r_ > 1 || g_ < 0 || g_ > 1 || b_ < 0 || b_ > 1) {
throw new RangeError("'r', 'g' and 'b' shouldn't be uppon their respective maximum values .");
}
const k_ = 1 - Math.max(r_, g_, b_)
let c_ = (1 - r_ - k_) / (1 - k_)
let m_ = (1 - g_ - k_) / (1 - k_)
let y_ = (1 - b_ - k_) / (1 - k_)
c_ = isNaN(c_) ? 0 : c_
m_ = isNaN(m_) ? 0 : m_
y_ = isNaN(y_) ? 0 : y_
console.log(c_, m_, y_, k_)
let result = {
c: c_ * maximums.cmyk.c,
m: m_ * maximums.cmyk.m,
y: y_ * maximums.cmyk.y,
k: k_ * maximums.cmyk.k,
}
return result
}
/**
* A set of maximum values for HSL and CMYK.
* @typedef {object} hslCmykMaximums
* @prop {cmykMaximums} [cmyk={ c: 1, m: 1, y: 1, k: 1 }] The CMYK maximum values
* @prop {hslMaximums} [hsl={ h: 360, s: 100, l: 100 }] The HSL maximum values
*/
/**
* This function converts an HSL color set into CMYK.
* @param {number} h The Hue value
* @param {number} s The Saturation value
* @param {number} l The Lightness value
* @param {hslCmykMaximums} [maximums={ hsl: { h: 360, s: 100, l: 100 }, cmyk: { c: 1, m: 1, y: 1, k: 1 } }] The maximum values for HSL (input) and CMYK (output)
* @returns {cmyk}
*/
export function hslToCmyk(h, s, l, maximums = {}) {
maximums.hsl = maximums.hsl ?? {}
maximums.cmyk = maximums.cmyk ?? {}
maximums.hsl.h = maximums.hsl.h || 360
maximums.hsl.s = maximums.hsl.s || 100
maximums.hsl.l = maximums.hsl.l || 100
maximums.cmyk.c = maximums.cmyk.c || 1
maximums.cmyk.m = maximums.cmyk.m || 1
maximums.cmyk.y = maximums.cmyk.y || 1
maximums.cmyk.k = maximums.cmyk.k || 1
let rgb = hslToRgb(h, s, l, { hsl: maximums.hsl })
return rgbToCmyk(rgb.r, rgb.g, rgb.b, { cmyk: maximums.cmyk })
}
/**
* This function converts a CMYK color set into HSL.
* @param {number} c The Cyan value
* @param {number} m The Magenta value
* @param {number} y The Yellow value
* @param {number} k The Key value
* @param {hslCmykMaximums} [maximums={ hsl: { h: 360, s: 100, l: 100 }, cmyk: { c: 1, m: 1, y: 1, k: 1 } }] The maximum values for CMYK (input) and HSL (output)
* @returns {hsl}
*/
export function cmykToHsl(c, m, y, k, maximums = {}) {
maximums.hsl = maximums.hsl ?? {}
maximums.cmyk = maximums.cmyk ?? {}
maximums.hsl.h = maximums.hsl.h || 360
maximums.hsl.s = maximums.hsl.s || 100
maximums.hsl.l = maximums.hsl.l || 100
maximums.cmyk.c = maximums.cmyk.c || 1
maximums.cmyk.m = maximums.cmyk.m || 1
maximums.cmyk.y = maximums.cmyk.y || 1
maximums.cmyk.k = maximums.cmyk.k || 1
let rgb = cmykToRgb(c, m, y, k, { cmyk: maximums.cmyk })
return rgbToHsl(rgb.r, rgb.g, rgb.b, { hsl: maximums.hsl })
}
/**
* This function converts from hexadecimal color into RGB.
* @param {string} hex The hexadecimal color, in the format <code>#XXXXXX</code> or <code>XXXXXX</code>
* @param {rgbMaximums} maximums The RGB (output) maximum values
* @returns {rgb}
*/
export function hexToRgb(hex, maximums = {}) {
maximums.r = maximums.r ?? 255;
maximums.g = maximums.g ?? 255;
maximums.b = maximums.b ?? 255;
if (!/^#?[A-F0-9]{6}$/i.test(hex)) {
return { r: NaN, g: NaN, b: NaN }
}
hex = hex.replace("#", "")
/** @type {{r: string, g: string, b: string}} */
let rgbHex = {
r: hex.slice(0, 2),
g: hex.slice(2, 4),
b: hex.slice(4, 6)
}
return {
r: parseInt(rgbHex.r, 16) / 255 * maximums.r,
g: parseInt(rgbHex.g, 16) / 255 * maximums.g,
b: parseInt(rgbHex.b, 16) / 255 * maximums.b
}
}
/**
* This function converts from RGB color set into hexadecimal.
* @param {number} r The Red value
* @param {number} g The Green value
* @param {number} b The Blue value
* @param {rgbMaximums} maximums The RGB (output) maximum values
* @returns {string}
*/
export function rgbToHex(r, g, b, maximums = {}) {
maximums.r = maximums.r ?? 255;
maximums.g = maximums.g ?? 255;
maximums.b = maximums.b ?? 255;
let r_ = r / maximums.r * 255
let g_ = g / maximums.g * 255
let b_ = b / maximums.b * 255
if (!haveValidType("number", r_, g_, b_)) {
return ""
}
if (r_ < 0 || r_ > 255 || g_ < 0 || g_ > 255 || b_ < 0 || b_ > 255) {
throw new RangeError("'r', 'g' and 'b' shouldn't be uppon their respective maximum values .");
}
return (
"#" +
r_.toString(16).padStart(2, "0") +
g_.toString(16).padStart(2, "0") +
b_.toString(16).padStart(2, "0")
)
}