chromaticity-color-utilities
Version:
Color utilities for Node.js
1,358 lines • 69.4 kB
JavaScript
"use strict";
// chromaticity-color-utilities
// Copyright (C) 2022 Emma Litwa-Vulcu
//
// This program is free software: you can ristribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var Colors = __importStar(require("./Colors"));
var Util_1 = __importDefault(require("./Util"));
var Reference_1 = require("./Reference");
var Convert = /** @class */ (function () {
function Convert() {
}
/////////// NORMALIZE & GAMMA ///////////
/**
* Normalize RGB values to 0-1
*
* @param {Colors.rgb} rgb
* @return {Colors.rgbNormalized}
*/
Convert.rgbNormalize = function (rgb) {
return new Colors.rgbNormalized(rgb.getR() / rgb.getMax(), rgb.getG() / rgb.getMax(), rgb.getB() / rgb.getMax(), rgb.getA() / rgb.getMax());
};
/**
* Apply gamma to normalized RGB value
* NOT to be used with sRGB, L*, or other color spaces that utilize companding transformation formulae
*
* @param {Colors.rgbNormalized} rgb
* @param {number} gamma
* @return {Colors.rgbNormalized}
*/
Convert.applyGamma = function (rgb, gamma) {
var gammaN;
if (typeof gamma === 'number') {
gammaN = gamma;
}
else if (typeof Reference_1.colorSpaces[gamma]['gamma'] === 'number') {
gammaN = Reference_1.colorSpaces[gamma]['gamma'];
}
else {
throw new Error('Gamma not found for specificed color space');
}
var r = Math.pow(rgb.getR(), gammaN);
var g = Math.pow(rgb.getG(), gammaN);
var b = Math.pow(rgb.getB(), gammaN);
return new Colors.rgbNormalized(r, g, b, rgb.getA(), gammaN);
};
/////////// HUE, SATURATION, VALUE/LIGHTNESS/BRIGHTNESS ///////////
/**
* Convert RGB to HSV
* Saturation and Value are in percentages
*
* @param {Colors.rgb} rgb
* @param {boolean} [round=true]
* @return {Colors.hsv}
*/
Convert.rgb2hsv = function (rgb, round) {
if (round === void 0) { round = true; }
var r = rgb.getR() / rgb.getMax();
var g = rgb.getG() / rgb.getMax();
var b = rgb.getB() / rgb.getMax();
var max = Math.max(r, g, b);
var min = Math.min(r, g, b);
var range = max - min;
var val = (max / 1) * 100;
var sat = max ? (range / max) * 100 : 0;
var hue;
if (!range)
hue = 0;
else if (r == max)
hue = (g - b) / range;
else if (g == max)
hue = (b - r) / range + 2;
else if (b == max)
hue = (r - g) / range + 4;
else
hue = 0;
hue *= 60;
while (hue >= 360)
hue -= 360;
while (hue < 0)
hue += 360;
var a = Util_1.default.scaleValueRange(rgb.getA(), 0, rgb.getMax(), 0, 100);
if (round) {
hue = Math.round(hue);
sat = Math.round(sat);
val = Math.round(val);
a = Math.round(a);
}
return new Colors.hsv(hue, sat, val, a);
};
/**
* Convert RGB to HSL
* Saturation and Lightness are in percentages
*
* @param {Colors.rgb} rgb
* @param {boolean} [round=true]
* @return {Colors.hsl}
*/
Convert.rgb2hsl = function (rgb, round) {
if (round === void 0) { round = true; }
var r = rgb.getR() / rgb.getMax();
var g = rgb.getG() / rgb.getMax();
var b = rgb.getB() / rgb.getMax();
var max = Math.max(r, g, b);
var min = Math.min(r, g, b);
var chroma = max - min;
var lit = (max + min) / 2;
var sat = (lit == 0 || lit == 1 ? 0 : (max - lit) / Math.min(lit, 1 - lit)) * 100;
lit *= 100;
var hue;
if (!chroma)
hue = 0;
else if (max == r)
hue = (g - b) / chroma;
else if (max == g)
hue = (b - r) / chroma + 2;
else if (max == b)
hue = (r - g) / chroma + 4;
else
hue = 0;
hue *= 60;
while (hue >= 360)
hue -= 360;
while (hue < 0)
hue += 360;
var a = Util_1.default.scaleValueRange(rgb.getA(), 0, rgb.getMax(), 0, 100);
if (round) {
hue = Math.round(hue);
sat = Math.round(sat);
lit = Math.round(lit);
a = Math.round(a);
}
return new Colors.hsl(hue, sat, lit, a);
};
/**
* Convert RGB to HSI
* Saturation and Intensity are in percentages
*
* @param {Colors.rgb} rgb
* @param {boolean} [round=true]
* @return {Colors.hsi}
*/
Convert.rgb2hsi = function (rgb, round) {
if (round === void 0) { round = true; }
var r = rgb.getR() / rgb.getMax();
var g = rgb.getG() / rgb.getMax();
var b = rgb.getB() / rgb.getMax();
var i = (r + g + b) / 3;
var h = 0, s = 0;
if (i) {
s = 1 - Math.min(r, g, b) / i;
h =
Math.atan2((Math.sqrt(3) / 2) * (g - b), 0.5 * (2 * r - g - b)) *
(180 / Math.PI);
if (h < 0)
h += 360;
s = Math.min(Math.max(s, 0), 1);
i = Math.min(Math.max(i, 0), 1);
s *= 100;
i *= 100;
}
var a = Util_1.default.scaleValueRange(rgb.getA(), 0, rgb.getMax(), 0, 100, round);
if (round) {
h = Math.round(h);
s = Math.round(s);
i = Math.round(i);
}
return new Colors.hsi(h, s, i, a);
};
/**
* Convert RGB to HSI
* Saturation and Intensity are in percentages
*
* @param {Colors.rgb} rgb
* @param {boolean} [round=true]
* @return {Colors.hsi}
*/
Convert.rgb2hsi_deprecated = function (rgb, round) {
if (round === void 0) { round = true; }
var r = rgb.getR() / rgb.getMax();
var g = rgb.getG() / rgb.getMax();
var b = rgb.getB() / rgb.getMax();
var max = Math.max(r, g, b);
var min = Math.min(r, g, b);
var chroma = max - min;
var int = (r + g + b) * (1 / 3);
var hue;
var sat;
if (!chroma) {
hue = 0;
sat = 0;
}
else {
if (max == r)
hue = Util_1.default.fmod((g - b) / chroma, 6);
else if (max == g)
hue = (b - r) / chroma + 2;
else
hue = (r - g) / chroma + 4;
hue *= 60;
while (hue >= 360)
hue -= 360;
while (hue < 0)
hue += 360;
sat = chroma && int ? (1 - min / int) * 100 : 0;
}
int *= 100;
var a = Util_1.default.scaleValueRange(rgb.getA(), 0, rgb.getMax(), 0, 100);
if (round) {
hue = Math.round(hue);
sat = Math.round(sat);
int = Math.round(int);
a = Math.round(a);
}
return new Colors.hsi(hue, sat, int, a);
};
/**
* Convert HSV to RGB
* Saturation and Value should be in percentages
*
* @param {Colors.hsv} hsv
* @param {boolean} [round=true]
* @param {number} [bitDepth=8]
* @return {Colors.rgb}
*/
Convert.hsv2rgb = function (hsv, round, bitDepth) {
if (round === void 0) { round = true; }
if (bitDepth === void 0) { bitDepth = 8; }
var r, g, b;
var max = Math.pow(2, bitDepth) - 1;
if (hsv.getS() == 0) {
var all = hsv.getV() / 100;
r = all;
g = all;
b = all;
}
else {
var h = hsv.getH() / 60;
var s = hsv.getS() / 100;
var v = hsv.getV() / 100;
var i = Math.floor(h);
var f = h - i;
var p = v * (1 - s);
var q = v * (1 - s * f);
var t = v * (1 - s * (1 - f));
switch (i) {
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
default:
r = v;
g = p;
b = q;
}
}
r = Math.min(Math.max(r, 0), 1);
g = Math.min(Math.max(g, 0), 1);
b = Math.min(Math.max(b, 0), 1);
r *= max;
g *= max;
b *= max;
var a = Util_1.default.scaleValueRange(hsv.getA(), 0, 100, 0, max, round);
if (round) {
r = Math.round(r);
g = Math.round(g);
b = Math.round(b);
a = Math.round(a);
}
return new Colors.rgb(r, g, b, a, bitDepth);
};
/**
* Convert HSV to HSL
* Saturation and Value should be in percentages
* Saturation and Lightness are in percentages
*
* @param {Colors.hsv} hsv
* @param {boolean} [round=true]
* @return {Colors.hsl}
*/
Convert.hsv2hsl = function (hsv, round) {
if (round === void 0) { round = true; }
var s = hsv.getS() / 100;
var v = hsv.getV() / 100;
var lit = v * (1 - s / 2);
var sat;
if (lit == 0 || lit == 1) {
sat = 0;
}
else {
sat = (v - lit) / Math.min(lit, 1 - lit);
}
lit *= 100;
sat *= 100;
if (round) {
lit = Math.round(lit);
sat = Math.round(sat);
}
return new Colors.hsl(hsv.getH(), sat, lit, hsv.getA());
};
/**
* Convert HSV to HSI
* Saturation(V) and Value should be in percentages
* Saturation(I) and Intensity will be in percentages
*
* @param {Colors.hsv} hsv
* @param {boolean} [round=true]
* @return {Colors.hsi}
*/
Convert.hsv2hsi = function (hsv, round) {
if (round === void 0) { round = true; }
var rgb = this.hsv2rgb(hsv, false);
var hsi = this.rgb2hsi(rgb, round);
return hsi;
};
/**
* Convert HSL to HSV
* Saturation and Lightness should be in percentages
* Saturation and Value are in percentages
*
* @param {Colors.hsl} hsl
* @param {boolean} [round=true]
* @return {Colors.hsv}
*/
Convert.hsl2hsv = function (hsl, round) {
if (round === void 0) { round = true; }
var s = hsl.getS() / 100;
var l = hsl.getL() / 100;
var val = l + s * Math.min(l, 1 - l);
var sat = val ? 2 * (1 - l / val) : 0;
val *= 100;
sat *= 100;
if (round) {
val = Math.round(val);
sat = Math.round(sat);
}
return new Colors.hsv(hsl.getH(), sat, val, hsl.getA());
};
/**
* Convert HSL to RGB
* Saturation and Lightness should be in percentages
*
* @param {Colors.hsl} hsl
* @param {boolean} [round=true]
* @param {number} [bitDepth=8]
* @return {Colors.rgb}
*/
Convert.hsl2rgb = function (hsl, round, bitDepth) {
if (round === void 0) { round = true; }
if (bitDepth === void 0) { bitDepth = 8; }
var h = hsl.getH() / 60;
var s = hsl.getS() / 100;
var l = hsl.getL() / 100;
var r;
var g;
var b;
if (!s) {
r = l;
g = l;
b = l;
}
else {
var chroma = (1 - Math.abs(2 * l - 1)) * s;
var huef = Math.floor(h);
var huefmod = Number((h - Math.floor(h / 2) * 2).toPrecision(8));
var x = chroma * (1 - Math.abs(huefmod - 1));
var m = l - chroma / 2;
switch (huef) {
case 0:
r = chroma + m;
g = x + m;
b = m;
break;
case 1:
r = x + m;
g = chroma + m;
b = m;
break;
case 2:
r = m;
g = chroma + m;
b = x + m;
break;
case 3:
r = m;
g = x + m;
b = chroma + m;
break;
case 4:
r = x + m;
g = m;
b = chroma + m;
break;
case 5:
r = chroma + m;
g = m;
b = x + m;
break;
default:
r = m;
g = m;
b = m;
}
}
r = Math.min(Math.max(r, 0), 1);
g = Math.min(Math.max(g, 0), 1);
b = Math.min(Math.max(b, 0), 1);
var max = Math.pow(2, bitDepth) - 1;
r *= max;
g *= max;
b *= max;
var a = Util_1.default.scaleValueRange(hsl.getA(), 0, 100, 0, max, round);
if (round) {
r = Math.round(r);
g = Math.round(g);
b = Math.round(b);
a = Math.round(a);
}
return new Colors.rgb(r, g, b, a, bitDepth);
};
/**
* Convert HSL to HSI
* Saturation(L) and Lightness should be in percentages
* Saturation(I) and Intensity will be in percentages
*
* @param {Colors.hsl} hsl
* @param {boolean} [round=true]
* @return {Colors.hsi}
*/
Convert.hsl2hsi = function (hsl, round) {
if (round === void 0) { round = true; }
var rgb = this.hsl2rgb(hsl, false);
var hsi = this.rgb2hsi(rgb, round);
return hsi;
};
/**
* Convert HSI to RGB
* Saturation and Intensity should be in percentages
*
* @param {Colors.hsi} hsi
* @param {boolean} [round=true]
* @param {number} [bitDepth=8]
* @return {Colors.rgb}
*/
Convert.hsi2rgb = function (hsi, round, bitDepth) {
if (round === void 0) { round = true; }
if (bitDepth === void 0) { bitDepth = 8; }
var h = hsi.getH() - 360 * Math.floor(hsi.getH() / 360);
var s = hsi.getS() / 100;
var i = hsi.getI() / 100;
var r, g, b;
if (h < 120) {
b = i * (1 - s);
r =
i *
(1 +
(s * Math.cos(h * (Math.PI / 180))) /
Math.cos((60 - h) * (Math.PI / 180)));
g = 3 * i - r - b;
}
else if (h < 240) {
h = h - 120;
r = i * (1 - s);
g =
i *
(1 +
(s * Math.cos(h * (Math.PI / 180))) /
Math.cos((60 - h) * (Math.PI / 180)));
b = 3 * i - r - g;
}
else {
h = h - 240;
g = i * (1 - s);
b =
i *
(1 +
(s * Math.cos(h * (Math.PI / 180))) /
Math.cos((60 - h) * (Math.PI / 180)));
r = 3 * i - g - b;
}
r = Math.min(Math.max(r, 0), 1);
g = Math.min(Math.max(g, 0), 1);
b = Math.min(Math.max(b, 0), 1);
var max = Math.pow(2, bitDepth) - 1;
r *= max;
g *= max;
b *= max;
if (round) {
r = Math.round(r);
g = Math.round(g);
b = Math.round(b);
}
var a = Util_1.default.scaleValueRange(hsi.getA(), 0, 100, 0, Math.pow(2, bitDepth) - 1, round);
return new Colors.rgb(r, g, b, a, bitDepth);
};
/**
* Convert HSI to RGB
* Saturation and Intensity should be in percentages
*
* @param {Colors.hsi} hsi
* @param {boolean} [round=true]
* @param {number} [bitDepth=8]
* @return {Colors.rgb}
*/
Convert.hsi2rgb_deprecated = function (hsi, round, bitDepth) {
if (round === void 0) { round = true; }
if (bitDepth === void 0) { bitDepth = 8; }
var h = hsi.getH() / 60;
var s = hsi.getS() / 100;
var i = hsi.getI() / 100;
var m = i * (1 - s);
var r;
var g;
var b;
if (!s) {
r = m;
g = m;
b = m;
}
else {
var hfmod2 = Number((h - Math.floor(h / 2) * 2).toPrecision(8));
var z = 1 - Math.abs(hfmod2 - 1);
var chroma = (3 * i * s) / (1 + z);
var x = chroma * z;
var huef = Math.floor(h);
switch (huef) {
case 0:
r = chroma + m;
g = x + m;
b = m;
break;
case 1:
r = x + m;
g = chroma + m;
b = m;
break;
case 2:
r = m;
g = chroma + m;
b = x + m;
break;
case 3:
r = m;
g = x + m;
b = chroma + m;
break;
case 4:
r = x + m;
g = m;
b = chroma + m;
break;
case 5:
r = chroma + m;
g = m;
b = x + m;
break;
default:
r = m;
g = m;
b = m;
}
}
r = Math.min(Math.max(r, 0), 1);
g = Math.min(Math.max(g, 0), 1);
b = Math.min(Math.max(b, 0), 1);
var max = Math.pow(2, bitDepth) - 1;
r *= max;
g *= max;
b *= max;
var a = Util_1.default.scaleValueRange(hsi.getA(), 0, 100, 0, max, round);
if (round) {
r = Math.round(r);
g = Math.round(g);
b = Math.round(b);
a = Math.round(a);
}
return new Colors.rgb(r, g, b, a, bitDepth);
};
/**
* Convert RGB to HSP
* Saturation and Perceived Brightness will be in percentages
*
* @param {Colors.rgb} rgb
* @param {boolean} [round=true]
* @return {Colors.hsp}
*/
Convert.rgb2hsp = function (rgb, round, Pb, Pr) {
if (round === void 0) { round = true; }
if (Pb === void 0) { Pb = 0.114; }
if (Pr === void 0) { Pr = 0.299; }
if (Pr + Pb > 1) {
throw new Error('Pr + Pg + Pb must = 1');
}
var Pg = 1 - Pr - Pb;
var r = rgb.getR() / rgb.getMax();
var g = rgb.getG() / rgb.getMax();
var b = rgb.getB() / rgb.getMax();
var pb = Math.sqrt(Math.pow(r, 2) * Pr + Math.pow(g, 2) * Pg + Math.pow(b, 2) * Pb);
var value = Math.max(r, g, b);
var chroma = value - Math.min(r, g, b);
var s = value ? chroma / value : 0;
var h;
if (!chroma)
h = 0;
else if (value == r)
h = (g - b) / chroma;
else if (value == g)
h = (b - r) / chroma + 2;
else if (value == b)
h = (r - g) / chroma + 4;
else
h = 0;
h *= 60;
while (h >= 360)
h -= 360;
while (h < 0)
h += 360;
s *= 100;
pb *= 100;
var a = Util_1.default.scaleValueRange(rgb.getA(), 0, rgb.getMax(), 0, 100, round);
if (round) {
h = Math.round(h);
s = Math.round(s);
pb = Math.round(pb);
a = Math.round(a);
}
return new Colors.hsp(h, s, pb, a, Pb, Pr);
};
/**
* Convert HSP to RGB
* Saturation and Perceived Brightness should be in percentages
*
* @param {Colors.hsp} hsp
* @param {boolean} [round=true]
* @return {Colors.rgb}
*/
Convert.hsp2rgb = function (hsp, round, bitDepth) {
if (round === void 0) { round = true; }
if (bitDepth === void 0) { bitDepth = 8; }
var hp = hsp.getH() / 60;
var s = hsp.getS() / 100;
var pb = hsp.getP() / 100;
var s0 = 1 - s;
var r, g, b;
var hpf = Math.floor(hp);
// console.log(s0, hpf)
var hpp;
if (s0 > 0) {
switch (hpf) {
case 0: //R>G>B
hpp = hp;
b =
pb /
Math.sqrt(hsp.getPr() / Math.pow(s0, 2) +
hsp.getPg() * Math.pow(1 + hpp * (1 / s0 - 1), 2) +
hsp.getPb());
r = b / s0;
g = b + hpp * (r - b);
break;
case 1: //G>R>B
hpp = -1 * hp + 2;
b =
pb /
Math.sqrt(hsp.getPg() / Math.pow(s0, 2) +
hsp.getPr() * Math.pow(1 + hpp * (1 / s0 - 1), 2) +
hsp.getPb());
g = b / s0;
r = b + hpp * (g - b);
break;
case 2: //G>B>R
hpp = hp - 2;
r =
pb /
Math.sqrt(hsp.getPg() / Math.pow(s0, 2) +
hsp.getPb() * Math.pow(1 + hpp * (1 / s0 - 1), 2) +
hsp.getPr());
g = r / s0;
b = r + hpp * (g - r);
break;
case 3: //B>G>R
hpp = -1 * hp + 4;
r =
pb /
Math.sqrt(hsp.getPb() / Math.pow(s0, 2) +
hsp.getPg() * Math.pow(1 + hpp * (1 / s0 - 1), 2) +
hsp.getPr());
b = r / s0;
g = r + hpp * (b - r);
break;
case 4: //B>R>G
hpp = hp - 4;
g =
pb /
Math.sqrt(hsp.getPb() / Math.pow(s0, 2) +
hsp.getPr() * Math.pow(1 + hpp * (1 / s0 - 1), 2) +
hsp.getPg());
b = g / s0;
r = g + hpp * (b - g);
break;
case 5: //R>B>G
default:
hpp = -1 * hp + 6;
g =
pb /
Math.sqrt(hsp.getPr() / Math.pow(s0, 2) +
hsp.getPb() * Math.pow(1 + hpp * (1 / s0 - 1), 2) +
hsp.getPg());
r = g / s0;
b = g + hpp * (r - g);
}
}
else {
switch (hpf) {
case 0: //R>G>B
hpp = hp;
r = Math.sqrt(Math.pow(pb, 2) / (hsp.getPr() + hsp.getPg() * Math.pow(hpp, 2)));
g = r * hpp;
b = 0;
break;
case 1: //G>R>B
hpp = -1 * hp + 2;
g = Math.sqrt(Math.pow(pb, 2) / (hsp.getPg() + hsp.getPr() * Math.pow(hpp, 2)));
r = g * hpp;
b = 0;
break;
case 2: //G>B>R
hpp = hp - 2;
g = Math.sqrt(Math.pow(pb, 2) / (hsp.getPg() + hsp.getPb() * Math.pow(hpp, 2)));
b = g * hpp;
r = 0;
break;
case 3: //B>G>R
hpp = -1 * hp + 4;
b = Math.sqrt(Math.pow(pb, 2) / (hsp.getPb() + hsp.getPg() * Math.pow(hpp, 2)));
g = b * hpp;
r = 0;
break;
case 4: //B>R>G
hpp = hp - 4;
b = Math.sqrt(Math.pow(pb, 2) / (hsp.getPb() + hsp.getPr() * Math.pow(hpp, 2)));
r = b * hpp;
g = 0;
break;
case 5: //R>B>G
default:
hpp = -1 * hp + 6;
r = Math.sqrt(Math.pow(pb, 2) / (hsp.getPr() + hsp.getPb() * Math.pow(hpp, 2)));
b = r * hpp;
g = 0;
}
}
r = Math.min(Math.max(r, 0), 1);
g = Math.min(Math.max(g, 0), 1);
b = Math.min(Math.max(b, 0), 1);
var max = Math.pow(2, bitDepth) - 1;
r *= max;
g *= max;
b *= max;
var a = Util_1.default.scaleValueRange(hsp.getA(), 0, 100, 0, max, round);
if (round) {
r = Math.round(r);
g = Math.round(g);
b = Math.round(b);
a = Math.round(a);
}
return new Colors.rgb(r, g, b, a, bitDepth);
};
/////////// CMYK ///////////
/**
* Convert RGB to CMYK
* This conversion is mathematical and does not take pigment conversion into account
*
* @param {Colors.rgb} rgb
* @param {boolean} [round=true]
* @param {number} [bitDepth=255] RGB max value per channel
* @return {Colors.cmyk}
*/
Convert.rgb2cmyk = function (rgb, round) {
if (round === void 0) { round = true; }
var r = rgb.getR() / rgb.getMax();
var g = rgb.getG() / rgb.getMax();
var b = rgb.getB() / rgb.getMax();
//todo: if alpha, blend value with white
var c;
var m;
var y;
var k = 1 - Math.max(r, g, b);
if (k == 1) {
c = 0;
m = 0;
y = 0;
}
else {
c = ((1 - r - k) / (1 - k)) * 100;
m = ((1 - g - k) / (1 - k)) * 100;
y = ((1 - b - k) / (1 - k)) * 100;
}
k *= 100;
if (round) {
c = Math.round(c);
m = Math.round(m);
y = Math.round(y);
k = Math.round(k);
}
return new Colors.cmyk(c, m, y, k);
};
/**
* Convert CMYK to RGB
* This conversion is mathematical and does not take pigment conversion into account
*
* @param {Colors.cmyk} cmyk
* @param {boolean} [round=true]
* @param {number} [bitDepth=8]
* @return {Colors.rgb}
*/
Convert.cmyk2rgb = function (cmyk, round, bitDepth) {
if (round === void 0) { round = true; }
if (bitDepth === void 0) { bitDepth = 8; }
var c = cmyk.getC() / 100;
var m = cmyk.getM() / 100;
var y = cmyk.getY() / 100;
var k = cmyk.getK() / 100;
var max = Math.pow(2, bitDepth) - 1;
var r = (1 - c) * (1 - k) * max;
var g = (1 - m) * (1 - k) * max;
var b = (1 - y) * (1 - k) * max;
if (round) {
r = Math.round(r);
g = Math.round(g);
b = Math.round(b);
}
return new Colors.rgb(r, g, b, max, bitDepth);
};
/////////// YIQ ///////////
/**
* Convert RGB to YIQ
* TODO: Validate Algorithm
*
* @param {Colors.rgb} rgb
* @param {boolean} [normalize=true] true = Y[0,255], I&Q[-128,128]; false = Y[0,1], I[-0.5957,0.5957], Q[-0.5226,0.5226]
* @param {boolean} [round=true] will not round if not normalized
* @return {Colors.yiq}
*/
Convert.rgb2yiq = function (rgb, normalize, round) {
if (normalize === void 0) { normalize = true; }
if (round === void 0) { round = true; }
var r = rgb.getR() / rgb.getMax();
var g = rgb.getG() / rgb.getMax();
var b = rgb.getB() / rgb.getMax();
var y = 0.299 * r + 0.587 * g + 0.114 * b;
var i = 0.5959 * r + -0.2746 * g + -0.3213 * b;
var q = 0.2115 * r + -0.5227 * g + 0.3112 * b;
y = Math.min(Math.max(y, 0), 1);
i = Math.min(Math.max(i, -0.5957), 0.5957);
q = Math.min(Math.max(q, -0.5226), 0.5226);
if (normalize) {
y = Util_1.default.scaleValueRange(y, 0, 1, 0, 255, false);
i = Util_1.default.scaleValueRange(i + 0.5957, 0, 1.1914, 0, 256, false) - 128;
q = Util_1.default.scaleValueRange(q + 0.5226, 0, 1.0452, 0, 256, false) - 128;
if (round) {
y = Math.round(y);
i = Math.round(i);
q = Math.round(q);
}
}
return new Colors.yiq(y, i, q, normalize);
};
/**
* Convert YIQ to RGB
*
* @param {Colors.yiq} yiq
* @param {boolean} [round=true]
* @param {number} [bitDepth=8]
* @return {Colors.rgb}
*/
Convert.yiq2rgb = function (yiq, round, bitDepth) {
if (round === void 0) { round = true; }
if (bitDepth === void 0) { bitDepth = 8; }
var y = yiq.getY();
var i = yiq.getI();
var q = yiq.getQ();
if (yiq.isNormalized()) {
y = Util_1.default.scaleValueRange(y, 0, 255, 0, 1, false);
i = Util_1.default.scaleValueRange(i, -128, 128, -0.5957, 0.5957, false);
q = Util_1.default.scaleValueRange(q, -128, 128, -0.5226, 0.5226, false);
}
var r = y + 0.956 * i + 0.621 * q;
var g = y + -0.272 * i + -0.647 * q;
var b = y + -1.106 * i + 1.703 * q;
r = Math.min(Math.max(r, 0), 1);
g = Math.min(Math.max(g, 0), 1);
b = Math.min(Math.max(b, 0), 1);
var max = Math.pow(2, bitDepth) - 1;
r *= max;
g *= max;
b *= max;
if (round) {
r = Math.round(r);
g = Math.round(g);
b = Math.round(b);
}
return new Colors.rgb(r, g, b, max, bitDepth);
};
/////////// XYZ, xyY ///////////
/**
* Convert RGB to XYZ
* X, Y, and Z will be between 0 and the white point reference XYZ values
*
* @param {Colors.rgb} rgb
* @param {string} [colorSpace=srgb] RGB color space (e.g. sRGB)
* @param {string} [referenceWhite=d65] RGB reference white (e.g. D65)
* @return {Colors.xyz}
*/
Convert.rgb2xyz = function (rgb, colorSpace, referenceWhite) {
if (colorSpace === void 0) { colorSpace = 'srgb'; }
if (referenceWhite === void 0) { referenceWhite = 'd65'; }
var r = rgb.getR() / rgb.getMax();
var g = rgb.getG() / rgb.getMax();
var b = rgb.getB() / rgb.getMax();
var space = Util_1.default.validColorSpace(colorSpace);
referenceWhite = referenceWhite.toLowerCase();
if (typeof space['rgb2xyz'] == 'undefined' ||
typeof space['rgb2xyz'][referenceWhite] == 'undefined') {
throw new Error('Transformation matrix unavailable for this color space and reference white');
}
var m = space['rgb2xyz'][referenceWhite];
if (colorSpace == 'srgb') {
// sRGB
r = r <= 0.04045 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4);
g = g <= 0.04045 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4);
b = b <= 0.04045 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4);
}
else if (colorSpace == 'ecirgb') {
// L*
r = r <= 0.08 ? 100 * (r / Reference_1.cieK) : Math.pow((r + 0.16) / 1.16, 3);
g = g <= 0.08 ? 100 * (g / Reference_1.cieK) : Math.pow((g + 0.16) / 1.16, 3);
b = b <= 0.08 ? 100 * (b / Reference_1.cieK) : Math.pow((b + 0.16) / 1.16, 3);
}
else {
// Gamma
if (typeof space['gamma'] == 'undefined') {
throw new Error('Gamma not defined for this color space');
}
var gamma = space['gamma'];
r = Math.pow(r, gamma);
g = Math.pow(g, gamma);
b = Math.pow(b, gamma);
}
// [X] [R]
// [Y] = [M 3x3]*[G]
// [Z] [B]
var x = m[0][0] * r + m[0][1] * g + m[0][2] * b;
var y = m[1][0] * r + m[1][1] * g + m[1][2] * b;
var z = m[2][0] * r + m[2][1] * g + m[2][2] * b;
return new Colors.xyz(x, y, z);
};
/**
* Convert XYZ to RGB
* RGB values that fall outsize representable values will be clamped
*
* @param {Colors.xyz} xyz
* @param {boolean} [round=true]
* @param {number} [bitDepth=8]
* @return {Colors.rgb}
*/
Convert.xyz2rgb = function (xyz, colorSpace, referenceWhite, round, bitDepth) {
if (colorSpace === void 0) { colorSpace = 'srgb'; }
if (referenceWhite === void 0) { referenceWhite = 'd65'; }
if (round === void 0) { round = true; }
if (bitDepth === void 0) { bitDepth = 8; }
var space = Util_1.default.validColorSpace(colorSpace);
referenceWhite = referenceWhite.toLowerCase();
if (typeof space['xyz2rgb'] == 'undefined' ||
typeof space['xyz2rgb'][referenceWhite] == 'undefined') {
throw new Error('Transformation matrix unavailable for this color space and reference white');
}
var m = space['xyz2rgb'][referenceWhite];
// [R] [X]
// [G] = [M]*[Y] where [M] is [RGB to XYZ matrix]^-1
// [B] [Z]
var r = m[0][0] * xyz.getX() + m[0][1] * xyz.getY() + m[0][2] * xyz.getZ();
var g = m[1][0] * xyz.getX() + m[1][1] * xyz.getY() + m[1][2] * xyz.getZ();
var b = m[2][0] * xyz.getX() + m[2][1] * xyz.getY() + m[2][2] * xyz.getZ();
if (colorSpace == 'srgb') {
// sRGB
r = r <= 0.0031308 ? r * 12.92 : 1.055 * Math.pow(r, 1 / 2.4) - 0.055;
g = g <= 0.0031308 ? g * 12.92 : 1.055 * Math.pow(g, 1 / 2.4) - 0.055;
b = b <= 0.0031308 ? b * 12.92 : 1.055 * Math.pow(b, 1 / 2.4) - 0.055;
}
else if (colorSpace == 'ecirgb') {
// L*
r = r <= Reference_1.cieE ? (r * Reference_1.cieK) / 100 : 1.16 * Math.pow(r, 1 / 3) - 0.16;
r = g <= Reference_1.cieE ? (g * Reference_1.cieK) / 100 : 1.16 * Math.pow(g, 1 / 3) - 0.16;
r = b <= Reference_1.cieE ? (b * Reference_1.cieK) / 100 : 1.16 * Math.pow(b, 1 / 3) - 0.16;
}
else {
// Gamma
if (typeof space['gamma'] == 'undefined') {
throw new Error('Gamma not defined for this color space');
}
r = Math.pow(r, 1 / space['gamma']);
g = Math.pow(g, 1 / space['gamma']);
b = Math.pow(b, 1 / space['gamma']);
}
var max = Math.pow(2, bitDepth) - 1;
r = Math.min(Math.max(r, 0), 1) * max;
g = Math.min(Math.max(g, 0), 1) * max;
b = Math.min(Math.max(b, 0), 1) * max;
if (round) {
r = Math.round(r);
g = Math.round(g);
b = Math.round(b);
}
return new Colors.rgb(r, g, b, max, bitDepth);
};
/**
* Convert XYZ to xyY
* If X = Z = Y = 0, set x and y to chromaticity coordinates of reference white
*
* @param {Colors.xyz} xyz
* @return {Colors.xyy}
*/
Convert.xyz2xyy = function (xyz, referenceWhite) {
if (referenceWhite === void 0) { referenceWhite = 'd65'; }
var w = Util_1.default.validReferenceWhite(referenceWhite);
var cx;
var cy;
var sum = xyz.getX() + xyz.getY() + xyz.getZ();
if (!sum) {
cx = w.x;
cy = w.y;
}
else {
cx = xyz.getX() / sum;
cy = xyz.getY() / sum;
}
return new Colors.xyy(cx, cy, xyz.getY());
};
/**
* Convert xyY to XYZ
*
* @param {Colors.xyy} xyy
* @return {Colors.xyz}
*/
Convert.xyy2xyz = function (xyy) {
var cx, cz;
if (!xyy.getYY()) {
cx = 0;
cz = 0;
}
else {
cx = (xyy.getX() * xyy.getYY()) / xyy.getY();
cz = ((1 - xyy.getX() - xyy.getY()) * xyy.getYY()) / xyy.getY();
}
return new Colors.xyz(cx, xyy.getYY(), cz);
};
/////////// Lab ///////////
/**
* Convert XYZ to Lab
*
* @param {Colors.xyz} xyz
* @param {string} referenceWhite
* @param {boolean} [round=true]
* @return {Colors.lab}
*/
Convert.xyz2lab = function (xyz, referenceWhite, round) {
if (referenceWhite === void 0) { referenceWhite = 'd65'; }
if (round === void 0) { round = true; }
var w = Util_1.default.validReferenceWhite(referenceWhite);
var xr = xyz.getX() / w.x;
var yr = xyz.getY() / w.y;
var zr = xyz.getZ() / w.z;
var fx = xr > Reference_1.cieE ? Math.pow(xr, 1 / 3) : (Reference_1.cieK * xr + 16) / 116;
var fy = yr > Reference_1.cieE ? Math.pow(yr, 1 / 3) : (Reference_1.cieK * yr + 16) / 116;
var fz = zr > Reference_1.cieE ? Math.pow(zr, 1 / 3) : (Reference_1.cieK * zr + 16) / 116;
var l = 116 * fy - 16;
var a = 500 * (fx - fy);
var b = 200 * (fy - fz);
l = Math.max(l, 0); // specular white can be over 100
if (round) {
l = Math.round(l);
a = Math.round(a);
b = Math.round(b);
}
return new Colors.lab(l, a, b);
};
/**
* Convert Lab to XYZ
*
* @param {Colors.lab} lab
* @return {Colors.xyz}
*/
Convert.lab2xyz = function (lab, referenceWhite) {
if (referenceWhite === void 0) { referenceWhite = 'd65'; }
var w = Util_1.default.validReferenceWhite(referenceWhite);
var lr = (lab.getL() + 16) / 116; // y
var ar = lab.getA() / 500 + lr; // x
var br = lr - lab.getB() / 200; // z
var xr = Math.pow(ar, 3) > Reference_1.cieE ? Math.pow(ar, 3) : (116 * ar - 16) / Reference_1.cieK;
// the following two y(r) formulae seem to be equivalent??? somehow???
// let yr = lab.getL() > cieK * cieE ? Math.pow(lr, 3) : lab.getL() / cieK
var yr = Math.pow(lr, 3) > Reference_1.cieE ? Math.pow(lr, 3) : (116 * lr - 16) / Reference_1.cieK;
var zr = Math.pow(br, 3) > Reference_1.cieE ? Math.pow(br, 3) : (116 * br - 16) / Reference_1.cieK;
var x = xr * w.x;
var y = yr * w.y;
var z = zr * w.z;
return new Colors.xyz(x, y, z);
};
/////////// Luv ///////////
/**
* Convert XYZ to Luv
* L will range between 0% and 100%
* u and v will range between -100% and 100%
*
* @param {Colors.xyz} xyz
* @return {Colors.luv}
*/
Convert.xyz2luv = function (xyz, referenceWhite, round) {
if (referenceWhite === void 0) { referenceWhite = 'd65'; }
if (round === void 0) { round = true; }
var w = Util_1.default.validReferenceWhite(referenceWhite);
var yr = xyz.getY() / w.y;
var div = xyz.getX() + 15 * xyz.getY() + 3 * xyz.getZ();
var up, vp;
if (!div) {
up = 0;
vp = 0;
}
else {
up = (4 * xyz.getX()) / div;
vp = (9 * xyz.getY()) / div;
}
var upr = (4 * w.x) / (w.x + 15 * w.y + 3 * w.z);
var vpr = (9 * w.y) / (w.x + 15 * w.y + 3 * w.z);
var l = yr > Reference_1.cieE ? 116 * Math.pow(yr, 1 / 3) - 16 : Reference_1.cieK * yr;
var u = 13 * l * (up - upr);
var v = 13 * l * (vp - vpr);
l = Math.min(Math.max(l, 0), 100);
if (round) {
l = Math.round(l);
u = Math.round(u);
v = Math.round(v);
}
return new Colors.luv(l, u, v);
};
/**
* Convert Luv to XYZ
* X, Y, and Z will be in range 0 to 1
*
* @param {Colors.luv} luv
* @return {Colors.xyz}
*/
Convert.luv2xyz = function (luv, referenceWhite) {
if (referenceWhite === void 0) { referenceWhite = 'd65'; }
var w = Util_1.default.validReferenceWhite(referenceWhite);
var u0 = (4 * w.x) / (w.x + 15 * w.y + 3 * w.z);
var v0 = (9 * w.y) / (w.x + 15 * w.y + 3 * w.z);
var y = luv.getL() > Reference_1.cieK * Reference_1.cieE
? Math.pow((luv.getL() + 16) / 116, 3)
: luv.getL() / Reference_1.cieK;
var ad = luv.getU() + 13 * luv.getL() * u0;
var adf = ad ? (52 * luv.getL()) / ad : 0;
var a = (1 / 3) * (adf - 1);
var x = a + 1 / 3
? (y * ((39 * luv.getL()) / (luv.getV() + 13 * luv.getL() * v0) - 5) +
5 * y) /
(a + 1 / 3)
: 0;
var z = x * a - 5 * y;
return new Colors.xyz(x, y, z);
};
/////////// YCbCr and STANDARDS ///////////
/**
* Convert RGB to Rec709 RGB
* Will output either 8-bit or 10-bit depending on input color space
*
* @param {Colors.rgb} rgb
* @param {boolean} [round=true]
* @param {number} [bitRate=8]
* @return {Colors.rec709rgb}
*/
Convert.rgb2rec709rgb = function (rgb, round, bitRate) {
if (round === void 0) { round = true; }
if (bitRate === void 0) { bitRate = 8; }
// output must be 8-bit or 10-bit, pick whichever is closer to input depth
var rgbLower, rgbUpper;
if (bitRate == 8) {
rgbLower = 16;
rgbUpper = 235;
}
else if (bitRate == 10) {
rgbLower = 64;
rgbUpper = 940;
}
else {
throw new Error('Invalid bitrate for Rec709, must be 8 or 10');
}
var r = Util_1.default.scaleValueRange(rgb.getR(), 0, rgb.getMax(), rgbLower, rgbUpper, round);
var g = Util_1.default.scaleValueRange(rgb.getG(), 0, rgb.getMax(), rgbLower, rgbUpper, round);
var b = Util_1.default.scaleValueRange(rgb.getB(), 0, rgb.getMax(), rgbLower, rgbUpper, round);
var a = Util_1.default.scaleValueRange(rgb.getA(), 0, rgb.getMax(), 0, Math.pow(2, bitRate) - 1, round);
return new Colors.rec709rgb(r, g, b, a, bitRate);
};
/**
* Convert Rec709 RGB to RGB
* Converts 8-bit or 10-bit Rec709 RGB values to standard (0 - $color_depth) range
* Input RGB values outside of legal black and white points will be clamped
*
* @param {Colors.rec709rgb} rgb709
* @param {boolean} [round=true]
* @param {number} [bitDepth=8]
* @return {Colors.rgb}
*/
Convert.rec709rgb2rgb = function (rgb709, round, bitDepth) {
if (round === void 0) { round = true; }
if (bitDepth === void 0) { bitDepth = 8; }
var minFrom, maxFrom;
if (rgb709.getBitDepth() == 8) {
minFrom = 16;
maxFrom = 235;
}
else if (rgb709.getBitDepth() == 10) {
minFrom = 64;
maxFrom = 940;
}
else {
throw new Error('Invalid bitrate (must be 8 or 10)');
}
// Rather than require bounds, clamp values
var r709 = Math.min(Math.max(rgb709.getR(), minFrom), maxFrom);
var g709 = Math.min(Math.max(rgb709.getG(), minFrom), maxFrom);
var b709 = Math.min(Math.max(rgb709.getB(), minFrom), maxFrom);
var a709 = Math.min(Math.max(rgb709.getA(), minFrom), maxFrom);
var max = Math.pow(2, bitDepth) - 1;
var r = Util_1.default.scaleValueRange(r709, minFrom, maxFrom, 0, max, round);
var g = Util_1.default.scaleValueRange(g709, minFrom, maxFrom, 0, max, round);
var b = Util_1.default.scaleValueRange(b709, minFrom, maxFrom, 0, max, round);
var a = Util_1.default.scaleValueRange(a709, 0, rgb709.getMax(), 0, max, round);
return new Colors.rgb(r, g, b, a, bitDepth);
};
/**
* Convert RGB to Rec2020 RGB
* Will output either 10-bit or 12-bit depending on input color space
*
* @param {Colors.rgb} rgb
* @param {boolean} [round=true]
* @param {number} [bitRate=10]
* @return {Colors.rec2020rgb}
*/
Convert.rgb2rec2020rgb = function (rgb, round, bitRate) {
if (round === void 0) { round = true; }
if (bitRate === void 0) { bitRate = 10; }
// output must be 10-bit or 12-bit, pick whichever is closer to input depth
var rgbLower, rgbUpper;
if (bitRate == 10) {
rgbLower = 64;
rgbUpper = 940;
}
else if (bitRate == 12) {
rgbLower = 256;
rgbUpper = 3760;
}
else {
throw new Error('Invalid bitrate for Rec2020, must be 10 or 12');
}
var r = Util_1.default.scaleValueRange(rgb.getR(), 0, rgb.getMax(), rgbLower, rgbUpper, round);
var g = Util_1.default.scaleValueRange(rgb.getG(), 0, rgb.getMax(), rgbLower, rgbUpper, round);
var b = Util_1.default.scaleValueRange(rgb.getB(), 0, rgb.getMax(), rgbLower, rgbUpper, round);
var a = Util_1.default.scaleValueRange(rgb.getA(), 0, rgb.getMax(), 0, Math.pow(2, bitRate) - 1, round);
return new Colors.rec2020rgb(r, g, b, a, bitRate);
};
/**
* Convert Rec2020 RGB to RGB
* Converts 10-bit or 12-bit Rec2020 RGB values to standard (0 - $color_depth) range
* Input RGB values outside of legal black and white points will be clamped
*
* @param {Colors.rec2020rgb} rgb2020
* @param {boolean} [round=true]
* @param {number} [bitDepth=8]
* @return {Colors.rgb}
*/
Convert.rec2020rgb2rgb = function (rgb2020, round, bitDepth) {
if (round === void 0) { round = true; }
if (bitDepth === void 0) { bitDepth = 8; }
var minFrom, maxFrom;
if (rgb2020.getBitDepth() == 10) {
minFrom = 64;
maxFrom = 940;
}
else if (rgb2020.getBitDepth() == 12) {
minFrom = 256;
maxFrom = 3760;
}
else {
throw new Error('Invalid bitrate (must be 10 or 12)');
}
// Rather than require bounds, clamp values
var r2020 = Math.min(Math.max(rgb2020.getR(), minFrom), maxFrom);
var g2020 = Math.min(Math.max(rgb2020.getG(), minFrom), maxFrom);
va