chromaticity-color-utilities
Version:
Color utilities for Node.js
556 lines (555 loc) • 25.4 kB
JavaScript
"use strict";
// chromaticity-color-utilities
// Copyright (C) 2022 Emma Litwa-Vulcu
//
// This program is free software: you can redistribute 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 Modify_1 = __importDefault(require("./Modify"));
var Blend_1 = __importDefault(require("./Blend"));
var Colors = __importStar(require("./Colors"));
var Harmony = /** @class */ (function () {
function Harmony() {
}
/**
* Return 180deg complement of color
*
* @param {Colors.hsv} hsv
* @return {Colors.hsv[]}
*/
Harmony.complement = function (hsv) {
var hueComplement = Modify_1.default.hueShift(hsv.getH(), 180);
return [hsv, new Colors.hsv(hueComplement, hsv.getS(), hsv.getV())];
};
/**
* Return an analogous color scheme based on input color and angle
*
* @param {Colors.hsv} hsv
* @param {number} [angle=30] degrees
* @return {Array<Colors.hsv>}
*/
Harmony.analogous = function (hsv, angle) {
if (angle === void 0) { angle = 30; }
var aHue1 = Modify_1.default.hueShift(hsv.getH(), angle);
var aHue2 = Modify_1.default.hueShift(hsv.getH(), angle * -1);
return [
hsv,
new Colors.hsv(aHue1, hsv.getS(), hsv.getV()),
new Colors.hsv(aHue2, hsv.getS(), hsv.getV()),
];
};
/**
* Return a triadic color scheme based on input color
* Alias of analogous() with 120deg angle
*
* @param {Colors.hsv} hsv
* @param {number} [angle=120]
* @return {Array<Colors.hsv>}
*/
Harmony.triadic = function (hsv, angle) {
if (angle === void 0) { angle = 120; }
return this.analogous(hsv, angle);
};
/**
* Return a split complement color scheme based on input color and angle
* Alias of analogous() but with different default angle
*
* @param {Colors.hsv} hsv
* @param {number} [angle=150] degrees
* @return {Array<Colors.hsv>}
*/
Harmony.splitComplement = function (hsv, angle) {
if (angle === void 0) { angle = 150; }
return this.analogous(hsv, angle);
};
/**
* Return a tetradic color scheme based on input color and angle
*
* @param {Colors.hsv} hsv
* @param {number} [angle=45] degrees
* @return {Array<Colors.hsv>}
*/
Harmony.tetradic = function (hsv, angle) {
if (angle === void 0) { angle = 45; }
var hue2 = Modify_1.default.hueShift(hsv.getH(), angle);
var hue3 = Modify_1.default.hueShift(hsv.getH(), angle + 180);
var hue4 = Modify_1.default.hueShift(hsv.getH(), 180);
return [
hsv,
new Colors.hsv(hue2, hsv.getS(), hsv.getV()),
new Colors.hsv(hue3, hsv.getS(), hsv.getV()),
new Colors.hsv(hue4, hsv.getS(), hsv.getV()),
];
};
/**
* Return a square color scheme based on input color
* Alias of tetradic() with 90deg angle
*
* @param {Colors.hsv} hsv
* @return {Array<Colors.hsv>}
*/
Harmony.square = function (hsv) {
return this.tetradic(hsv, 90);
};
/**
* Returns an array of colors of a darker shade
*
* @param {T extends colorType} color
* @param {string} [method='hsl']
* @param {number} colors
* @param {number} [distanceToBlack=1] 0-1, where 1 is all the way to black
* @param {boolesn} [round=true]
* @returns {T[]}
*/
Harmony.shade = function (color, method, colors, distanceToBlack) {
if (method === void 0) { method = 'hsl'; }
if (distanceToBlack === void 0) { distanceToBlack = 1; }
var scheme = [];
if ([
'hsv',
'hsva',
'hsi',
'hsia',
'hsp',
'hspa',
'hsl',
'hsla',
'rgb',
'rgb2',
'cmyk',
'lab',
'luv',
].includes(method)) {
var type = method.replace(/[0-9]/, '').replace(/a$/, '');
var start = color.to(type, { round: false });
for (var n = 0; n < colors; n++) {
scheme.push(start.modify('darken', {
method: method,
amount: (n / (colors - 1)) * distanceToBlack,
round: false,
}));
}
}
else {
throw new Error('Invalid shade scheme method: ' + method);
}
return scheme;
};
/**
* Returns an array of colors of a lighter tint
*
* @param {T extends colorType} color
* @param {string} [method='hsl']
* @param {number} colors
* @param {number} [distanceToWhite=1] 0-1, where 1 is all the way to white
* @param {boolesn} [round=true]
* @returns {T[]}
*/
Harmony.tint = function (color, method, colors, distanceToWhite) {
if (method === void 0) { method = 'hsl'; }
if (distanceToWhite === void 0) { distanceToWhite = 1; }
var scheme = [];
if ([
'hsv',
'hsva',
'hsi',
'hsia',
'hsp',
'hspa',
'hsl',
'hsla',
'rgb',
'rgb2',
'cmyk',
'lab',
'luv',
].includes(method)) {
var type = method.replace(/[0-9]/, '').replace(/a$/, '');
var start = color.to(type, { round: false });
for (var n = 0; n < colors; n++) {
scheme.push(start.modify('lighten', {
method: method,
amount: (n / (colors - 1)) * distanceToWhite,
round: false,
}));
}
}
else {
throw new Error('Invalid tint scheme method: ' + method);
}
return scheme;
};
/**
* Returns an array of colors of darker shades and lighter tints
*
* @param {T extends colorType} color
* @param {string} method
* @param {number} colors
* @param {boolean} [round=true]
* @param {number} [distance=1] 0-1, where 1 is all the way to closest bound OR white, if distanceShade given
* @param {number} [distanceShade=1] 0-1, where 1 is all the way to black
* @returns
*/
Harmony.shadetint = function (color, method, colors, round, distance, distanceShade) {
if (round === void 0) { round = true; }
if (distance === void 0) { distance = 1; }
var scheme = [];
switch (method) {
case 'hsl':
case 'hsla':
var hsl = color.to('hsl', { round: false });
if (typeof distanceShade === 'undefined') {
if (100 - hsl.getL() < hsl.getL()) {
distanceShade = ((100 - hsl.getL()) / 50) * distance;
}
else {
distanceShade = distance;
distance = (hsl.getL() / 50) * distanceShade;
}
}
for (var i = 0; i < colors; i++) {
scheme.push(Modify_1.default.hslDarken(hsl, ((colors - i) / colors) * distanceShade).to(color.getType(), { round: round }));
}
scheme.push(hsl.to(color.getType(), { round: round }));
for (var i = 1; i <= colors; i++) {
scheme.push(Modify_1.default.hslLighten(hsl, (i / colors) * distance).to(color.getType(), { round: round }));
}
break;
case 'hsv':
case 'hsva':
var hsv = color.to('hsv', { round: false });
if (typeof distanceShade === 'undefined') {
if (100 - hsv.getV() < hsv.getV()) {
distanceShade = ((100 - hsv.getV()) / 50) * distance;
}
else {
distanceShade = distance;
distance = (hsv.getV() / 50) * distanceShade;
}
}
for (var i = 0; i < colors; i++) {
scheme.push(Modify_1.default.hsvDarken(hsv, ((colors - i) / colors) * distanceShade).to(color.getType(), { round: round }));
}
scheme.push(hsv.to(color.getType(), { round: round }));
for (var i = 1; i <= colors; i++) {
scheme.push(Modify_1.default.hsvLighten(hsv, (i / colors) * distance).to(color.getType(), { round: round }));
}
break;
case 'hsi':
case 'hsia':
var hsi = color.to('hsi', { round: false });
if (typeof distanceShade === 'undefined') {
if (100 - hsi.getI() < hsi.getI()) {
distanceShade = ((100 - hsi.getI()) / 50) * distance;
}
else {
distanceShade = distance;
distance = (hsi.getI() / 50) * distanceShade;
}
}
for (var i = 0; i < colors; i++) {
scheme.push(Modify_1.default.hsiDarken(hsi, ((colors - i) / colors) * distanceShade).to(color.getType(), { round: round }));
}
scheme.push(hsi.to(color.getType(), { round: round }));
for (var i = 1; i <= colors; i++) {
scheme.push(Modify_1.default.hsiLighten(hsi, (i / colors) * distance).to(color.getType(), { round: round }));
}
break;
case 'hsp':
case 'hspa':
var hsp = color.to('hsp', { round: false });
if (typeof distanceShade === 'undefined') {
if (100 - hsp.getP() < hsp.getP()) {
distanceShade = ((100 - hsp.getP()) / 50) * distance;
}
else {
distanceShade = distance;
distance = (hsp.getP() / 50) * distanceShade;
}
}
for (var i = 0; i < colors; i++) {
scheme.push(Modify_1.default.hspDarken(hsp, ((colors - i) / colors) * distanceShade).to(color.getType(), { round: round }));
}
scheme.push(hsp.to(color.getType(), { round: round }));
for (var i = 1; i <= colors; i++) {
scheme.push(Modify_1.default.hspLighten(hsp, (i / colors) * distance).to(color.getType(), { round: round }));
}
break;
case 'rgb2':
case 'rgba2':
var rgb2 = color.to('rgb', { round: false });
if (typeof distanceShade === 'undefined') {
var avg = (rgb2.getR() + rgb2.getG() + rgb2.getB()) / 3;
if (rgb2.getMax() - avg < avg) {
distanceShade =
((rgb2.getMax() -
Math.min(rgb2.getR(), rgb2.getG(), rgb2.getB())) /
rgb2.getMax() /
2) *
distance;
}
else {
distanceShade = distance;
distance =
(Math.max(rgb2.getR(), rgb2.getG(), rgb2.getB()) /
rgb2.getMax() /
2) *
distanceShade;
}
}
for (var i = 0; i < colors; i++) {
scheme.push(Modify_1.default.rgb2Darken(rgb2, ((colors - i) / colors) * distanceShade).to(color.getType(), { round: round }));
}
scheme.push(rgb2.to(color.getType(), { round: round }));
for (var i = 1; i <= colors; i++) {
scheme.push(Modify_1.default.rgb2Lighten(rgb2, (i / colors) * distance).to(color.getType(), { round: round }));
}
break;
case 'rgb':
case 'rgba':
var rgb = color.to('rgb', { round: false });
if (typeof distanceShade === 'undefined') {
var avg = (rgb.getR() + rgb.getG() + rgb.getB()) / 3;
if (rgb.getMax() - avg < avg) {
distanceShade =
((rgb.getMax() - Math.min(rgb.getR(), rgb.getG(), rgb.getB())) /
rgb.getMax() /
2) *
distance;
}
else {
distanceShade = distance;
distance =
(Math.max(rgb.getR(), rgb.getG(), rgb.getB()) /
rgb.getMax() /
2) *
distanceShade;
}
}
for (var i = 0; i < colors; i++) {
scheme.push(Modify_1.default.rgbDarken(rgb, ((colors - i) / colors) * distanceShade).to(color.getType(), { round: round }));
}
scheme.push(rgb.to(color.getType(), { round: round }));
for (var i = 1; i <= colors; i++) {
scheme.push(Modify_1.default.rgbLighten(rgb, (i / colors) * distance).to(color.getType(), { round: round }));
}
break;
case 'cmyk':
var cmyk = color.to('cmyk', { round: false });
if (typeof distanceShade === 'undefined') {
if (cmyk.getK() < 50) {
distanceShade = (cmyk.getK() / 50) * distance;
}
else {
distanceShade = distance;
distance = ((100 - cmyk.getK()) / 50) * distanceShade;
}
}
for (var i = 0; i < colors; i++) {
scheme.push(Modify_1.default.cmykDarken(cmyk, ((colors - i) / colors) * distanceShade).to(color.getType(), { round: round }));
}
scheme.push(cmyk.to(color.getType(), { round: round }));
for (var i = 1; i <= colors; i++) {
scheme.push(Modify_1.default.cmykLighten(cmyk, (i / colors) * distance).to(color.getType(), { round: round }));
}
break;
case 'lab':
var lab = color.to('lab', { round: false });
if (typeof distanceShade === 'undefined') {
if (lab.getL() < 50) {
distanceShade = (lab.getL() / 50) * distance;
}
else {
distanceShade = distance;
distance = ((100 - lab.getL()) / 50) * distanceShade;
}
}
for (var i = 0; i < colors; i++) {
scheme.push(Modify_1.default.labDarken(lab, ((colors - i) / colors) * distanceShade).to(color.getType(), { round: round }));
}
scheme.push(lab.to(color.getType(), { round: round }));
for (var i = 1; i <= colors; i++) {
scheme.push(Modify_1.default.labLighten(lab, (i / colors) * distance).to(color.getType(), { round: round }));
}
break;
case 'luv':
var luv = color.to('luv', { round: false });
if (typeof distanceShade === 'undefined') {
if (luv.getL() < 50) {
distanceShade = (luv.getL() / 50) * distance;
}
else {
distanceShade = distance;
distance = ((100 - luv.getL()) / 50) * distanceShade;
}
}
for (var i = 0; i < colors; i++) {
scheme.push(Modify_1.default.luvDarken(luv, ((colors - i) / colors) * distanceShade).to(color.getType(), { round: round }));
}
scheme.push(luv.to(color.getType(), { round: round }));
for (var i = 1; i <= colors; i++) {
scheme.push(Modify_1.default.luvLighten(luv, (i / colors) * distance).to(color.getType(), { round: round }));
}
break;
default:
throw new Error('Invalid method for generating color scheme');
}
return scheme;
};
/**
* Return an array of colors blended from color1 to color2
*
* @param {T extends colorType} color1
* @param {T extends colorType} color2
* @param {number} colors number of colors in scheme (including color1 and color2)
* @returns {T[]}
*/
Harmony.gradient = function (type, color1, color2, colors, round) {
if (round === void 0) { round = true; }
if (colors < 2) {
throw new Error('Unable to generate gradient with less than two colors');
}
type = type.toLowerCase();
var reachesColor2 = ![
'multiply',
'screen',
'overlay',
'softlight',
'colorburn',
'colordodge',
'vividlight',
'linearburn',
'lineardodge',
'linearlight',
'divide',
'addition',
'subtraction',
'difference',
'hue',
'value',
'lightness',
'intensity',
'perceivedbrightness',
'perceived',
].includes(type);
var inBetweenColors = reachesColor2 ? colors - 2 : colors - 1;
var gradient = [];
gradient.push(color1);
for (var i = 0; i < inBetweenColors; i++) {
var amount = (i + 1) / (inBetweenColors + 1);
if ([
'multiply',
'screen',
'overlay',
'softlight',
'colordodge',
'colorburn',
'vividlight',
'lineardodge',
'linearburn',
'linearlight',
'divide',
'addition',
'subtraction',
'difference',
].includes(type)) {
gradient.push(Blend_1.default.rgbBlendMode(color1.to('rgb', { round: false }), color2.to('rgb', { round: false }), amount, type).to(color1.getType(), { round: round }));
}
else {
switch (type) {
case 'rgb':
case 'rgba':
gradient.push(Blend_1.default.rgbBlend(color1.to('rgb', { round: false }), color2.to('rgb', { round: false }), amount).to(color1.getType(), { round: round }));
break;
case 'hue':
gradient.push(Blend_1.default.hueBlend(color1.to('hsv', { round: false }), color2.to('hsv', { round: false }), amount).to(color1.getType(), { round: round }));
break;
case 'hsv':
case 'hsva':
gradient.push(Blend_1.default.hsvBlend(color1.to('hsv', { round: false }), color2.to('hsv', { round: false }), amount).to(color1.getType(), { round: round }));
break;
case 'value':
gradient.push(Blend_1.default.valueBlend(color1.to('hsv', { round: false }), color2.to('hsv', { round: false }), amount).to(color1.getType(), { round: round }));
break;
case 'hsl':
case 'hsla':
gradient.push(Blend_1.default.hslBlend(color1.to('hsl', { round: false }), color2.to('hsl', { round: false }), amount).to(color1.getType(), { round: round }));
break;
case 'lightness':
gradient.push(Blend_1.default.lightnessBlend(color1.to('hsl', { round: false }), color2.to('hsl', { round: false }), amount).to(color1.getType(), { round: round }));
break;
case 'hsi':
case 'hsia':
gradient.push(Blend_1.default.hsiBlend(color1.to('hsi', { round: false }), color2.to('hsi', { round: false }), amount).to(color1.getType(), { round: round }));
break;
case 'intensity':
gradient.push(Blend_1.default.intensityBlend(color1.to('hsi', { round: false }), color2.to('hsi', { round: false }), amount).to(color1.getType(), { round: round }));
break;
case 'hsp':
case 'hspa':
gradient.push(Blend_1.default.hspBlend(color1.to('hsp', { round: false }), color2.to('hsp', { round: false }), amount).to(color1.getType(), { round: round }));
break;
case 'perceivedbrightness':
case 'perceived':
gradient.push(Blend_1.default.perceivedBrightnessBlend(color1.to('hsp', { round: false }), color2.to('hsp', { round: false }), amount).to(color1.getType(), { round: round }));
break;
case 'cmyk':
gradient.push(Blend_1.default.cmykBlend(color1.to('cmyk', { round: false }), color2.to('cmyk', { round: false }), amount).to(color1.getType(), { round: round }));
break;
case 'yiq':
gradient.push(Blend_1.default.yiqBlend(color1.to('yiq', { round: false }), color2.to('yiq', { round: false }), amount).to(color1.getType(), { round: round }));
break;
case 'lab':
gradient.push(Blend_1.default.labBlend(color1.to('lab', { round: false }), color2.to('lab', { round: false }), amount).to(color1.getType(), { round: round }));
break;
case 'luv':
gradient.push(Blend_1.default.luvBlend(color1.to('luv', { round: false }), color2.to('luv', { round: false }), amount).to(color1.getType(), { round: round }));
break;
default:
throw new Error('Unrecognized gradient method');
}
}
}
if (reachesColor2)
gradient.push(color2);
return gradient;
};
return Harmony;
}());
exports.default = Harmony;