color-math
Version:
expressions to manipulate colors
412 lines (338 loc) • 10.2 kB
JavaScript
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }
function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
var Chroma = require('chroma-js');
import ColorScale from './ColorScale';
import BlendMode from './BlendMode';
import ValueType from './ValueType';
var getColorBlender = function () {
var bs = {};
bs[BlendMode.None] = function (a) {
return a;
};
bs[BlendMode.Replace] = function (a, b) {
return b;
};
bs[BlendMode.Add] = function (a, b) {
return Math.min(a + b, 255);
};
bs[BlendMode.ColorBurn] = function (a, b) {
return b <= 0 ? 0 : Math.max(255 - (255 - a) * 255 / b, 0);
};
bs[BlendMode.ColorDodge] = function (a, b) {
return b >= 255 ? 255 : Math.min(a * 255 / (255 - b), 255);
};
bs[BlendMode.Darken] = function (a, b) {
return Math.min(a, b);
};
bs[BlendMode.Difference] = function (a, b) {
return Math.abs(a - b);
};
bs[BlendMode.Divide] = function (a, b) {
return Math.min(a / 255 / (b / 255) * 255, 255);
};
bs[BlendMode.Exclusion] = function (a, b) {
return 255 - ((255 - a) * (255 - b) / 255 + a * b / 255);
};
bs[BlendMode.HardLight] = function (a, b) {
return b < 128 ? 2 * a * b / 255 : 255 - 2 * (255 - a) * (255 - b) / 255;
};
bs[BlendMode.Lighten] = function (a, b) {
return Math.max(a, b);
};
bs[BlendMode.LinearBurn] = function (a, b) {
return Math.max(0, a + b - 255);
};
bs[BlendMode.LinearDodge] = function (a, b) {
return Math.min(a + b, 255);
};
bs[BlendMode.Multiply] = function (a, b) {
return a * b / 255;
};
bs[BlendMode.Negate] = function (a, b) {
return 255 - Math.abs(255 - a - b);
};
bs[BlendMode.Overlay] = function (a, b) {
return a < 128 ? 2 * a * b / 255 : 255 - 2 * (255 - a) * (255 - b) / 255;
};
bs[BlendMode.Screen] = function (a, b) {
return 255 - (255 - a) * (255 - b) / 255;
};
bs[BlendMode.SoftLight] = function (a, b) {
return a < 128 ? ((b >> 1) + 64) * a * (2 / 255) : 255 - (191 - (b >> 1)) * (255 - a) * (2 / 255);
};
bs[BlendMode.Subtract] = function (a, b) {
return Math.max(a - b, 0);
};
return function (mode) {
return bs[mode];
};
}();
export function blendColors(bg, fg, mode) {
var blender = getColorBlender(mode);
var bgComps = bg.rgba();
var fgComps = fg.rgba();
var resComps = [void 0, void 0, void 0, fgComps[3] + bgComps[3] * (1 - fgComps[3])];
for (var i = 0; i < 3; i++) {
var c1 = bgComps[i] / 255;
var c2 = fgComps[i] / 255;
var c = blender(bgComps[i], fgComps[i]) / 255;
if (resComps[3]) {
c = (fgComps[3] * c2 + bgComps[3] * (c1 - fgComps[3] * (c1 + c2 - c))) / resComps[3];
}
resComps[i] = c * 255;
}
var color = Chroma(resComps);
return color;
}
export function cmyToCmykArray(valuesOrC, m, y) {
var k = 1;
var c;
if (!Array.isArray(valuesOrC)) {
c = valuesOrC;
} else {
var _valuesOrC = _slicedToArray(valuesOrC, 3);
c = _valuesOrC[0];
m = _valuesOrC[1];
y = _valuesOrC[2];
}
if (c < k) {
k = c;
}
if (m < k) {
k = m;
}
if (y < k) {
k = y;
}
if (k === 1) {
c = 0;
m = 0;
y = 0;
} else {
c = (c - k) / (1 - k);
m = (m - k) / (1 - k);
y = (y - k) / (1 - k);
}
return [c, m, y, k];
}
export function cmyToCmyk(c, m, y) {
var values = cmyToCmykArray(c, m, y);
var color = Chroma(values, 'cmyk');
return color;
}
export function inverseColor(color) {
var color2 = cloneValue(color);
color2 = color2.set('rgb.r', 255 - color.get('rgb.r'));
color2 = color2.set('rgb.g', 255 - color.get('rgb.g'));
color2 = color2.set('rgb.b', 255 - color.get('rgb.b'));
return color2;
}
export function colorArithmeticOp(value1, value2, op) {
var color;
var n;
if (isColor(value1)) {
color = value1;
n = value2;
} else {
n = value1;
color = value2;
}
color = cloneValue(color);
color = color.set('rgb.r', op + n);
color = color.set('rgb.g', op + n);
color = color.set('rgb.b', op + n);
return color;
}
export function roundColorComps(color) {
var space = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'rgb';
var ranges = getColorSpaceParamsValidRanges(space);
var comps = color.get(space);
for (var i = 0; i < ranges.length; i++) {
if (ranges[i][1] - ranges[i][0] > 2) {
comps[i] = Math.round(comps[i]);
}
}
var res = Chroma(comps, space);
res = res.alpha(color.alpha());
return res;
}
export function getColorSpaceParamsValidRanges(space) {
switch (space) {
case 'rgb':
return [[0, 255], [0, 255], [0, 255]];
case 'cmy':
return [[0, 1], [0, 1], [0, 1]];
case 'cmyk':
return [[0, 1], [0, 1], [0, 1], [0, 1]];
case 'hsl':
return [[0, 360], [0, 1], [0, 1]];
case 'hsv':
return [[0, 360], [0, 1], [0, 1]];
case 'hsi':
return [[0, 360], [0, 1], [0, 1]];
case 'lab':
return [[0, 100], [-128, 127], [-128, 127]];
case 'lch':
return [[0, 100], [0, 140], [0, 360]];
case 'hcl':
return [[0, 360], [0, 140], [0, 100]];
default:
throw new SyntaxError("unknown namespace: ".concat(space.toUpperCase(), "."));
}
}
export function isColor(value) {
return value && Array.isArray(value._rgb) && value._rgb.length === 4;
}
export function formatColor(color) {
var appendName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
color = roundColorComps(color);
var hex = color.hex();
var hex8 = color.hex('rgba');
var alpha = color.alpha();
var name = color.name();
var s = alpha === 1 || isNaN(alpha) ? hex : hex8;
if (appendName && name !== hex) {
s += " (".concat(name, ")");
}
return s;
}
export function colorFromWavelength(wl) {
var r = 0;
var g = 0;
var b = 0;
var a = 1;
if (wl >= 380 && wl < 440) {
r = -1 * (wl - 440) / (440 - 380);
g = 0;
b = 1;
} else if (wl >= 440 && wl < 490) {
r = 0;
g = (wl - 440) / (490 - 440);
b = 1;
} else if (wl >= 490 && wl < 510) {
r = 0;
g = 1;
b = -1 * (wl - 510) / (510 - 490);
} else if (wl >= 510 && wl < 580) {
r = (wl - 510) / (580 - 510);
g = 1;
b = 0;
} else if (wl >= 580 && wl < 645) {
r = 1;
g = -1 * (wl - 645) / (645 - 580);
b = 0.0;
} else if (wl >= 645 && wl <= 780) {
r = 1;
g = 0;
b = 0;
}
if (wl > 780 || wl < 380) {
a = 0;
} else if (wl > 700) {
a = (780 - wl) / (780 - 700);
} else if (wl < 420) {
a = (wl - 380) / (420 - 380);
}
var color = Chroma([r, g, b, a], 'gl');
return color;
}
export function throwError(error, loc) {
var locStr = loc ? " (".concat(loc, ")") : '';
throw "Error".concat(locStr, ": ").concat(error, ".");
}
export function getType(value) {
if (typeof value === 'number') {
return ValueType.Number;
} else if (isColor(value)) {
return ValueType.Color;
} else if (Array.isArray(value)) {
if (value.length) {
if (value.every(function (v) {
return getType(v) === ValueType.Number;
})) {
return ValueType.NumberArray;
} else if (value.every(function (v) {
return getType(v) === ValueType.Color;
})) {
return ValueType.ColorArray;
}
}
return ValueType.Array;
} else if (value instanceof ColorScale) {
return ValueType.ColorScale;
}
}
export function forceType(value, type, loc) {
var valueType = getType(value);
var types = Array.isArray(type) ? type : [type];
if (types.every(function (t) {
return (t & valueType) !== t;
})) {
var strs = types.map(function (t) {
switch (t) {
case ValueType.Number:
return 'a number';
case ValueType.Color:
return 'a color';
case ValueType.ColorScale:
return 'a color scale';
case ValueType.Array:
return 'an array';
case ValueType.NumberArray:
return 'a number array';
case ValueType.ColorArray:
return 'a color array';
default:
throw new Error('invalid value type');
}
});
var msg = strs[0];
if (strs.length > 1) {
msg = strs.slice(0, -1).join(', ') + ' or ' + strs.slice(-1);
}
throwError("value is not ".concat(msg), loc);
}
return value;
}
export function forceRange(value, loc) {
if (getType(value) !== ValueType.NumberArray || value.length !== 2) {
throwError('operand is not valid numeric range', loc);
}
return value;
}
export function cloneValue(value) {
var type = getType(value);
switch (type) {
case ValueType.Color:
return Chroma(value.rgba());
case ValueType.ColorScale:
return value.clone();
case ValueType.Array:
case ValueType.NumberArray:
case ValueType.ColorArray:
return value.map(function (v) {
return cloneValue(v);
});
default:
return value;
}
}
export function forceNumInRange(value, minOrRange, maxOrLoc, loc) {
var min = Array.isArray(minOrRange) ? minOrRange[0] : minOrRange;
var max = Array.isArray(minOrRange) ? minOrRange[1] : maxOrLoc;
loc = Array.isArray(minOrRange) ? maxOrLoc : loc;
var n = forceType(value, ValueType.Number, loc);
if (n < min || n > max) {
throwError("number in a range [".concat(min, "..").concat(max, "] is expected, you provided: ").concat(n), loc);
}
return n;
}
export function getObjKey(obj, value) {
for (var key in obj) {
if ((!obj.hasOwnProperty || key in obj) && obj[key] === value) {
return key;
}
}
}