Skip to content

Instantly share code, notes, and snippets.

@Caellian
Last active August 18, 2021 05:10
Show Gist options
  • Save Caellian/bff67244fee443abf943724818fd138c to your computer and use it in GitHub Desktop.
Save Caellian/bff67244fee443abf943724818fd138c to your computer and use it in GitHub Desktop.
Color Lib
/*
* Copyright © 2021 Tin Švagelj <[email protected]>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
* software and associated documentation files (the “Software”), to deal in the Software
* without restriction, including without limitation the rights to use, copy, modify,
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be included in all copies
* or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// WARNING: This code is very untested and I rewrote it several times so there's likely
// several undiscovered bugs here. If you find them, I'd be grateful if you let
// me know.
/* eslint-disable @typescript-eslint/no-use-before-define */
const USE_CSS4 = false;
const USE_PRECISION = 3;
const PRESERVE_FORMAT = false;
const TERNARY_WHEEL_STEPS = 12;
const COLOR_WHEEL_STEP_DEG = 360 / TERNARY_WHEEL_STEPS;
// =================
// Utility methods
// =================
function invokeIf<T, R>(condition: boolean, on: T, f: (it: T) => R): T | R {
if (condition) {
return f(on);
} else {
return on;
}
}
function clamp(min: number, value: number, max: number) {
return Math.max(min, Math.min(value, max));
}
function parseFractional(valueString: string): number {
if (valueString.endsWith("%")) {
return clamp(0, parseInt(valueString), 100) / 100;
} else {
return clamp(
0,
parseFloat(valueString.substring(0, valueString.length - 1)),
1
);
}
}
function parseAngular(valueString: string): number {
// parses a hue phasor
if (valueString.endsWith("%") || valueString.includes(".")) {
return parseFractional(valueString);
} else {
let result = parseInt(valueString);
while (result < 0) {
result += 360;
}
while (result > 360) {
result -= 360;
}
return result / 360;
}
}
function objectHasAny(obj: object | undefined, fields: string[]): boolean {
if (obj == undefined) {
return false;
}
for (const f in fields) {
if (f in obj) {
return true;
}
}
return false;
}
function getVarField(obj: Record<string, unknown>, names: string[]): unknown {
if (obj == undefined) {
return false;
}
for (const n in names) {
if (n in obj) {
if (obj[n]) return obj[n];
}
}
return null;
}
const ColorMatches = [
{
re: /^rgba\((\d{1,3}),(\d{1,3}),(\d{1,3}),(\d{1,3})\)$/,
process: function(matches: string[]) {
return new ColorRGB(
parseInt(matches[1]),
parseInt(matches[2]),
parseInt(matches[3]),
parseInt(matches[4]) / 255
);
},
},
{
re: /^rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)$/,
process: function(matches: string[]) {
return new ColorRGB(
parseInt(matches[1]),
parseInt(matches[2]),
parseInt(matches[3])
);
},
},
{
re: /^#?([\w\d]{2})([\w\d]{2})([\w\d]{2})([\w\d]{2})$/,
process: function(matches: string[]) {
return new ColorRGB(
parseInt(matches[1], 16),
parseInt(matches[2], 16),
parseInt(matches[3], 16),
parseInt(matches[4], 16) / 255
);
},
},
{
re: /^#?([\w\d]{2})([\w\d]{2})([\w\d]{2})$/,
process: function(matches: string[]) {
return new ColorRGB(
parseInt(matches[1], 16),
parseInt(matches[2], 16),
parseInt(matches[3], 16)
);
},
},
{
re: /^#?([\w\d])([\w\d])([\w\d])([\w\d])$/,
process: function(matches: string[]) {
return new ColorRGB(
parseInt(matches[1] + matches[1], 16),
parseInt(matches[2] + matches[2], 16),
parseInt(matches[3] + matches[3], 16),
parseInt(matches[4] + matches[4], 16) / 255
);
},
},
{
re: /^#?([\w\d])([\w\d])([\w\d])$/,
process: function(matches: string[]) {
return new ColorRGB(
parseInt(matches[1] + matches[1], 16),
parseInt(matches[2] + matches[2], 16),
parseInt(matches[3] + matches[3], 16)
);
},
},
{
re: /^hsla\((\d+%?|\d+\.\d+),(\d+%?|\d+\.\d+),(\d+%?|\d+\.\d+),(\d+%?|\d+\.\d+)\)$/,
process: function(matches: string[]) {
return new ColorHSL(
parseAngular(matches[1]),
parseFractional(matches[2]),
parseFractional(matches[3]),
parseFractional(matches[4])
);
},
},
{
re: /^hsl\((\d+%?|\d+\.\d+),(\d+%?|\d+\.\d+),(\d+%?|\d+\.\d+)\)$/,
process: function(matches: string[]) {
return new ColorHSL(
parseAngular(matches[1]),
parseFractional(matches[2]),
parseFractional(matches[3])
);
},
},
// These are planned for CSS 4, but there's no reason why we can't support them already
{
re: /^cmyka\((\d+%?|\d+\.\d+),(\d+%?|\d+\.\d+),(\d+%?|\d+\.\d+),(\d+%?|\d+\.\d+),(\d+%?|\d+\.\d+)\)$/,
process: function(matches: string[]) {
return new ColorCMYK(
parseFractional(matches[1]),
parseFractional(matches[2]),
parseFractional(matches[3]),
parseFractional(matches[4]),
parseFractional(matches[5])
);
},
},
{
re: /^cmyk\((\d+%?|\d+\.\d+),(\d+%?|\d+\.\d+),(\d+%?|\d+\.\d+),(\d+%?|\d+\.\d+)\)$/,
process: function(matches: string[]) {
return new ColorCMYK(
parseFractional(matches[1]),
parseFractional(matches[2]),
parseFractional(matches[3]),
parseFractional(matches[4])
);
},
},
];
function hue2rgb(p: number, q: number, t: number) {
while (t < 0) t += 1;
while (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;
}
export enum ColorFormat {
RGB,
HSL,
CMYK,
}
export enum ColorHarmony {
Complementary,
Analogous,
Triad,
SplitComplementary,
Square,
}
export enum BlendMode {
Min,
Max,
Average,
Sum,
Default,
}
function blend(a: number, b: number, op: BlendMode): number | null {
switch (op) {
case BlendMode.Min:
return Math.min(a, b);
case BlendMode.Max:
return Math.max(a, b);
case BlendMode.Average:
return (a + b) / 2;
case BlendMode.Sum:
return a + b;
default:
return null;
}
}
// noinspection JSUnusedGlobalSymbols
abstract class Color {
readonly colorFormat: ColorFormat;
protected _alpha = 1;
protected constructor(colorFormat: ColorFormat) {
this.colorFormat = colorFormat;
}
convertInto(format: ColorFormat | undefined): Color | never {
if (format == undefined || this.colorFormat == format) {
return this;
}
switch (format) {
case ColorFormat.RGB:
return this.rgb;
case ColorFormat.HSL:
return this.hsl;
case ColorFormat.CMYK:
return this.cmyk;
}
}
transition(other: Color, percent: number): Color {
// convert beforehand so we perform max 2 conversions instead of 8
const thisHSL = this.hsl;
const otherHSL = other.hsl;
return invokeIf(
PRESERVE_FORMAT,
new ColorHSL(
thisHSL.hue / percent + otherHSL.hue * percent,
thisHSL.saturation / percent + otherHSL.saturation * percent,
thisHSL.lightness / percent + otherHSL.lightness * percent,
thisHSL.alpha / percent + otherHSL.alpha * percent
),
c => {
return c.convertInto(this.colorFormat);
}
);
}
steps(other: Color, n: number): Color[] {
// convert beforehand so we perform max 2 conversions instead of 8
const thisHSL = this.hsl;
const otherHSL = other.hsl;
const result = [];
const step = 1.0 / (n - 1);
for (let i = 0; i < n; i++) {
result.push(thisHSL.transition(otherHSL, i * step));
}
return result;
}
private wheelRelative(pos: number): Color {
const thisHSL = this.hsl;
return invokeIf(
PRESERVE_FORMAT,
new ColorHSL(
((thisHSL.hue * 360 + COLOR_WHEEL_STEP_DEG * pos) % 360) / 360,
thisHSL.saturation,
thisHSL.lightness,
thisHSL.alpha
),
c => {
return c.convertInto(this.colorFormat);
}
);
}
opposite(): Color {
return this.wheelRelative(TERNARY_WHEEL_STEPS / 2);
}
harmony(type: ColorHarmony): Color[] {
switch (type) {
case ColorHarmony.Complementary:
return [this.opposite()];
case ColorHarmony.Analogous:
return [this.wheelRelative(-1), this.copy(), this.wheelRelative(1)];
case ColorHarmony.Triad:
return [this.wheelRelative(-4), this.copy(), this.wheelRelative(4)];
case ColorHarmony.SplitComplementary:
return [this.wheelRelative(-5), this.copy(), this.wheelRelative(5)];
case ColorHarmony.Square:
return [
this.wheelRelative(-3),
this.copy(),
this.wheelRelative(3),
this.opposite(),
];
default:
return [];
}
}
/**
* @param n number of monochromatic variants to return
*/
monochromaticVariants(n: number): Color[] {
if (n <= 0) {
return [];
} else if (n == 1) {
return [this.copy()];
}
const thisHSL = this.hsl;
const lowerBound =
thisHSL.lightness < 0.5 ? thisHSL.lightness : 1 - thisHSL.lightness;
const upperBound = 1 - lowerBound;
if (n == 2) {
const opposite = this.copy();
opposite.lightness = upperBound;
return [this.copy(), opposite];
}
const boundSize = upperBound - lowerBound;
const idealSubdivision = 1.0 / n;
const divisionsBefore: number = (() => {
let closestDivision = 0;
let closestDistance = 1.0,
pos = 0;
for (; pos < n; pos++) {
if (pos * idealSubdivision < lowerBound) {
// If we don't break here, outermost division points will be outside of [0, 1]
// range.
break;
}
const currDist = Math.abs(lowerBound - pos * idealSubdivision);
if (closestDistance > currDist && pos * idealSubdivision < lowerBound) {
closestDivision = pos;
closestDistance = currDist;
} else {
console.warn(
"Should've already exited divisionsBefore eval. This is a bug."
);
break; // Should've already exited.
}
}
return closestDivision;
})();
const step = boundSize / (n - divisionsBefore * 2);
const result: Color[] = [];
const current = thisHSL.copy();
current.lightness = lowerBound - divisionsBefore * step;
for (let pos = 0; pos < n; pos++) {
result.push(
invokeIf(PRESERVE_FORMAT, current.copy(), c => {
return c.convertInto(this.colorFormat);
})
);
current.lightness += step;
}
return result;
}
properties() {
const h = this.hue;
return {
cold: h > 90 / 360 && h < 270 / 360,
dark: this.lightness < 0.5,
};
}
mixAdditive(
other: Color,
colorBlend: BlendMode = BlendMode.Sum,
alphaBlend: BlendMode = BlendMode.Default
) {
const thisRGB = this.rgb;
const otherRGB = other.rgb;
return invokeIf(
PRESERVE_FORMAT,
new ColorRGB(
clamp(0, blend(thisRGB.red, otherRGB.red, colorBlend) ?? 0, 255),
clamp(0, blend(thisRGB.green, otherRGB.green, colorBlend) ?? 0, 255),
clamp(0, blend(thisRGB.blue, otherRGB.blue, colorBlend) ?? 0, 255),
clamp(0, blend(thisRGB.alpha, otherRGB.alpha, alphaBlend) ?? 1, 1)
),
c => {
return c.convertInto(this.colorFormat);
}
);
}
mixSubtractive(
other: Color,
colorBlend: BlendMode = BlendMode.Sum,
alphaBlend: BlendMode = BlendMode.Default
) {
const thisCMYK = this.cmyk;
const otherCMYK = other.cmyk;
return invokeIf(
PRESERVE_FORMAT,
new ColorCMYK(
clamp(0, blend(thisCMYK.cyan, otherCMYK.cyan, colorBlend) ?? 0, 1),
clamp(
0,
blend(thisCMYK.magenta, otherCMYK.magenta, colorBlend) ?? 0,
1
),
clamp(0, blend(thisCMYK.yellow, otherCMYK.yellow, colorBlend) ?? 0, 1),
clamp(0, blend(thisCMYK.black, otherCMYK.black, colorBlend) ?? 0, 1),
clamp(0, blend(thisCMYK.alpha, otherCMYK.alpha, alphaBlend) ?? 1, 1)
),
c => {
return c.convertInto(this.colorFormat);
}
);
}
blend(other: Color) {
return this.mixAdditive(other, BlendMode.Average);
}
add(other: Color) {
return this.mixAdditive(other);
}
sub(other: Color) {
return this.mixSubtractive(other);
}
abstract copy(): Color;
abstract css(): string;
abstract get rgb(): ColorRGB;
abstract set rgb(value: ColorRGB);
abstract get hsl(): ColorHSL;
abstract set hsl(value: ColorHSL);
abstract get cmyk(): ColorCMYK;
abstract set cmyk(value: ColorCMYK);
get red(): number {
return this.rgb.red;
}
set red(value: number) {
const mutated = this.rgb;
mutated.red = value;
this.rgb = mutated;
}
get green(): number {
return this.rgb.green;
}
set green(value: number) {
const mutated = this.rgb;
mutated.green = value;
this.rgb = mutated;
}
get blue(): number {
return this.rgb.blue;
}
set blue(value: number) {
const mutated = this.rgb;
mutated.blue = value;
this.rgb = mutated;
}
get hue(): number {
return this.hsl.hue;
}
set hue(value: number) {
const mutated = this.hsl;
mutated.hue = value;
this.hsl = mutated;
}
get saturation(): number {
return this.hsl.saturation;
}
set saturation(value: number) {
const mutated = this.hsl;
mutated.saturation = value;
this.hsl = mutated;
}
get lightness(): number {
return this.hsl.lightness;
}
set lightness(value: number) {
const mutated = this.hsl;
mutated.lightness = value;
this.hsl = mutated;
}
get darkness(): number {
return 1 - this.hsl.lightness;
}
set darkness(value: number) {
const mutated = this.hsl;
mutated.lightness = 1 - value;
this.hsl = mutated;
}
get cyan(): number {
return this.cmyk.cyan;
}
set cyan(value: number) {
const mutated = this.cmyk;
mutated.cyan = value;
this.cmyk = mutated;
}
get magenta(): number {
return this.cmyk.magenta;
}
set magenta(value: number) {
const mutated = this.cmyk;
mutated.magenta = value;
this.cmyk = mutated;
}
get yellow(): number {
return this.cmyk.yellow;
}
set yellow(value: number) {
const mutated = this.cmyk;
mutated.yellow = value;
this.cmyk = mutated;
}
get black(): number {
return this.cmyk.black;
}
set black(value: number) {
const mutated = this.cmyk;
mutated.black = value;
this.cmyk = mutated;
}
get alpha(): number {
return this._alpha ?? 1.0;
}
set alpha(value: number) {
this._alpha = value;
}
hasAlpha(): boolean {
return this._alpha != null;
}
static parse(color: string): Color | null {
color = color.replace(/ /g, ""); // remove all spaces to simplify matching
color = color.toLowerCase();
for (let i = 0; i < ColorMatches.length; i++) {
const matches = ColorMatches[i].re.exec(color);
if (matches) {
return ColorMatches[i].process(matches);
}
}
return null;
}
from(value?: Record<string, unknown>): Color {
if (value == null) {
return new ColorRGB(0, 0, 0, 1);
}
const alpha: number =
(getVarField(value, ["a", "alpha", "_alpha"]) as number) ?? 1;
if (
objectHasAny(value, [
"r",
"red",
"_red",
"g",
"green",
"_green",
"b",
"blue",
"_blue",
])
) {
return new ColorRGB(
(getVarField(value, ["r", "red", "_red"]) as number) ?? 0,
(getVarField(value, ["g", "green", "_green"]) as number) ?? 0,
(getVarField(value, ["b", "blue", "_blue"]) as number) ?? 0,
alpha
);
}
if (
objectHasAny(value, [
"h",
"hue",
"_hue",
"s",
"saturation",
"_saturation",
"l",
"lightness",
"_lightness",
"luminescence",
"_luminescence",
"d",
"darkness",
"_darkness",
])
) {
return new ColorHSL(
(getVarField(value, ["h", "hue", "_hue"]) as number) ?? 0,
(getVarField(value, ["s", "saturation", "_saturation"]) as number) ?? 0,
(getVarField(value, [
"l",
"lightness",
"_lightness",
"luminescence",
"_luminescence",
]) as number) ??
1 -
((getVarField(value, ["d", "darkness", "_darkness"]) as number) ??
0),
alpha
);
}
if (
objectHasAny(value, [
"c",
"cyan",
"_cyan",
"m",
"magenta",
"_magenta",
"y",
"yellow",
"_yellow",
"k",
"black",
"_black",
])
) {
return new ColorCMYK(
(getVarField(value, ["c", "cyan", "_cyan"]) as number) ?? 0,
(getVarField(value, ["m", "magenta", "_magenta"]) as number) ?? 0,
(getVarField(value, ["y", "yellow", "_yellow"]) as number) ?? 0,
(getVarField(value, ["k", "black", "_black"]) as number) ?? 0,
alpha
);
}
if (typeof value == "string") {
const parsed = Color.parse(value as string);
if (parsed != null) return parsed;
}
return new ColorRGB(0, 0, 0, 1);
}
}
export default Color;
export class ColorRGB extends Color {
private _red: number;
private _green: number;
private _blue: number;
constructor(red: number, green: number, blue: number, alpha?: number | null) {
super(ColorFormat.RGB);
this._red = red;
this._green = green;
this._blue = blue;
this._alpha = alpha ?? 1;
}
copy(): ColorRGB {
return new ColorRGB(this._red, this._green, this._blue, this._alpha);
}
css(): string {
if (this._alpha != null && this._alpha < 1) {
return `rgba(${this._red}, ${this._green}, ${this._blue}, ${Math.round(
this._alpha * 255
)})`;
}
return `rgb(${this._red}, ${this._green}, ${this._blue})`;
}
get rgb(): ColorRGB {
return this;
}
set rgb(value: ColorRGB) {
this._red = value.red;
this._green = value.green;
this._blue = value.blue;
this._alpha = value.alpha;
}
get red(): number {
return this._red;
}
set red(value: number) {
this._red = value;
}
get green(): number {
return this._green;
}
set green(value: number) {
this._green = value;
}
get blue(): number {
return this._blue;
}
set blue(value: number) {
this._blue = value;
}
get hsl(): ColorHSL {
const r = this._red / 255,
g = this._green / 255,
b = this._blue / 255;
const max = Math.max(r, g, b),
min = Math.min(r, g, b);
const l = (max + min) / 2;
if (max == min) {
return new ColorHSL(0, 0, l, this._alpha);
} else {
let h;
const d = max - min;
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
default:
h = (r - g) / d + 4;
break;
}
return new ColorHSL(
h / 6,
l > 0.5 ? d / (2 - max - min) : d / (max + min),
l,
this._alpha
);
}
}
set hsl(value: ColorHSL) {
const vRGB = value.rgb;
this._red = vRGB.red;
this._green = vRGB.green;
this._blue = vRGB.blue;
this._alpha = vRGB.alpha;
}
get cmyk(): ColorCMYK {
const r = this._red / 255,
g = this._green / 255,
b = this._blue / 255;
const k = 1 - Math.max(r, g, b);
return new ColorCMYK(
(1 - r - k) / (1 - k),
(1 - g - k) / (1 - k),
(1 - b - k) / (1 - k),
k,
this._alpha
);
}
set cmyk(value: ColorCMYK) {
const vRGB = value.rgb;
this._red = vRGB.red;
this._green = vRGB.green;
this._blue = vRGB.blue;
this._alpha = vRGB.alpha;
}
}
export class ColorHSL extends Color {
private _hue: number;
private _saturation: number;
private _lightness: number;
constructor(
hue: number,
saturation: number,
lightness: number,
alpha?: number | null
) {
super(ColorFormat.HSL);
this._hue = hue;
this._saturation = saturation;
this._lightness = lightness;
this._alpha = alpha ?? 1;
}
copy(): ColorHSL {
return new ColorHSL(
this._hue,
this._saturation,
this._lightness,
this._alpha
);
}
css(): string {
if (this._alpha != null && this._alpha < 1) {
return `hsla(${this.hue}, ${this._saturation.toFixed(
USE_PRECISION
)}, ${this._lightness.toFixed(USE_PRECISION)}, ${this._alpha.toFixed(
USE_PRECISION
)})`;
}
return `hsl(${this.hue}, ${this._saturation.toFixed(
USE_PRECISION
)}, ${this._lightness.toFixed(USE_PRECISION)})`;
}
get hue(): number {
return Math.round(this._hue * 360);
}
set hue(value: number) {
this._hue = value / 360;
}
get saturation(): number {
return this._saturation;
}
set saturation(value: number) {
this._saturation = value;
}
get lightness(): number {
return this._lightness;
}
set lightness(value: number) {
this._lightness = value;
}
get rgb(): ColorRGB {
if (this._saturation == 0) {
return new ColorRGB(
this._lightness,
this._lightness,
this._lightness,
this._alpha
);
} else {
const q =
this._lightness < 0.5
? this._lightness * (1 + this._saturation)
: this._lightness +
this._saturation -
this._lightness * this._saturation;
const p = 2 * this._lightness - q;
return new ColorRGB(
Math.round(hue2rgb(p, q, this._hue + 1 / 3) * 255),
Math.round(hue2rgb(p, q, this._hue) * 255),
Math.round(hue2rgb(p, q, this._hue - 1 / 3) * 255),
this._alpha
);
}
}
set rgb(value: ColorRGB) {
const vHSL = value.hsl;
this._hue = vHSL.hue;
this._saturation = vHSL.saturation;
this._lightness = vHSL.lightness;
this._alpha = vHSL.alpha;
}
get hsl(): ColorHSL {
return this;
}
set hsl(value: ColorHSL) {
this._hue = value.hue;
this._saturation = value.saturation;
this._lightness = value.lightness;
this._alpha = value.alpha;
}
get cmyk(): ColorCMYK {
if (this._saturation == 0) {
return new ColorCMYK(0, 0, 0, this.darkness, this._alpha);
} else {
return this.rgb.cmyk;
}
}
set cmyk(value: ColorCMYK) {
const vHSL = value.hsl;
this._hue = vHSL.hue;
this._saturation = vHSL.saturation;
this._lightness = vHSL.lightness;
this._alpha = vHSL.alpha;
}
}
export class ColorCMYK extends Color {
private _cyan: number;
private _magenta: number;
private _yellow: number;
private _black: number;
constructor(
cyan: number,
magenta: number,
yellow: number,
black: number,
alpha?: number | null
) {
super(ColorFormat.CMYK);
this._cyan = cyan;
this._magenta = magenta;
this._yellow = yellow;
this._black = black;
this._alpha = alpha ?? 1;
}
copy(): ColorCMYK {
return new ColorCMYK(
this._cyan,
this._magenta,
this._yellow,
this._black,
this._alpha
);
}
css(): string {
if (!USE_CSS4) {
// Default to rgb string if CSS 4 isn't enabled yet
return this.rgb.css();
}
if (this._alpha != null && this._alpha < 1) {
return `cmyka(${this._cyan.toFixed(
USE_PRECISION
)}, ${this._magenta.toFixed(USE_PRECISION)}, ${this._yellow.toFixed(
USE_PRECISION
)}, ${this._black.toFixed(USE_PRECISION)}, ${this._alpha.toFixed(
USE_PRECISION
)})`;
}
return `cmyk(${this._cyan.toFixed(USE_PRECISION)}, ${this._magenta.toFixed(
USE_PRECISION
)}, ${this._yellow.toFixed(USE_PRECISION)}, ${this._black.toFixed(
USE_PRECISION
)})`;
}
get cyan(): number {
return this._cyan;
}
set cyan(value: number) {
this._cyan = value;
}
get magenta(): number {
return this._magenta;
}
set magenta(value: number) {
this._magenta = value;
}
get yellow(): number {
return this._yellow;
}
set yellow(value: number) {
this._yellow = value;
}
get black(): number {
return this._black;
}
set black(value: number) {
this._black = value;
}
get rgb(): ColorRGB {
return new ColorRGB(
255 * (1 - this._cyan) * (1 - this._black),
255 * (1 - this._magenta) * (1 - this._black),
255 * (1 - this._yellow) * (1 - this._black),
this._alpha
);
}
set rgb(value: ColorRGB) {
const vCMYK = value.cmyk;
this._cyan = vCMYK.cyan;
this._magenta = vCMYK.magenta;
this._yellow = vCMYK.yellow;
this._black = vCMYK.black;
this._alpha = vCMYK.alpha;
}
get hsl(): ColorHSL {
return this.rgb.hsl;
}
set hsl(value: ColorHSL) {
const vCMYK = value.cmyk;
this._cyan = vCMYK.cyan;
this._magenta = vCMYK.magenta;
this._yellow = vCMYK.yellow;
this._black = vCMYK.black;
this._alpha = vCMYK.alpha;
}
get cmyk(): ColorCMYK {
return this;
}
set cmyk(value: ColorCMYK) {
this._cyan = value.cyan;
this._magenta = value.magenta;
this._yellow = value.yellow;
this._black = value.black;
this._alpha = value.alpha;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment