colorjs.io
Version:
Let’s get serious about color
234 lines (199 loc) • 4.8 kB
JavaScript
/**
* @packageDocumentation
* @class Color
* Class that represents a single color.
* All of Color.js’s tree-shakeable methods are also available as instance methods on this class,
* as well as static methods that take the color as the first argument.
*/
import * as util from "./util.js";
import hooks from "./hooks.js";
import defaults from "./defaults.js";
import ColorSpace from "./ColorSpace.js";
import { WHITES } from "./adapt.js";
import {
getColor,
tryColor,
parse,
to,
serialize,
inGamut,
toGamut,
distance,
deltas,
equals,
get,
getAll,
set,
setAll,
display,
} from "./index-fn.js";
import "./spaces/xyz-d50.js";
import "./spaces/srgb.js";
export default class Color {
/**
* Creates an instance of Color.
* Signatures:
* - `new Color(stringToParse)`
* - `new Color(otherColor)`
* - `new Color({space, coords, alpha})`
* - `new Color(space, coords, alpha)`
* - `new Color(spaceId, coords, alpha)`
*/
constructor (...args) {
let color;
if (args.length === 1) {
let parseMeta = {};
// Clone simple objects to avoid mutating original in getColor
if (
typeof args[0] === "object" &&
Object.getPrototypeOf(args[0]).constructor === Object
) {
args[0] = { ...args[0] };
}
color = getColor(args[0], { parseMeta });
if (parseMeta.format) {
// Color actually came from a string
this.parseMeta = parseMeta;
}
}
let space, coords, alpha;
if (color) {
space = color.space || color.spaceId;
coords = color.coords;
alpha = color.alpha;
}
else {
// default signature new Color(ColorSpace, array [, alpha])
[space, coords, alpha] = args;
}
Object.defineProperty(this, "space", {
value: ColorSpace.get(space),
writable: false,
enumerable: true,
configurable: true, // see note in https://262.ecma-international.org/8.0/#sec-proxy-object-internal-methods-and-internal-slots-get-p-receiver
});
this.coords = coords ? coords.slice() : [0, 0, 0];
// Clamp alpha to [0, 1]
this.alpha = util.isNone(alpha) ? alpha : alpha === undefined ? 1 : util.clamp(0, alpha, 1);
// Define getters and setters for each coordinate
for (let id in this.space.coords) {
Object.defineProperty(this, id, {
get: () => this.get(id),
set: value => this.set(id, value),
});
}
}
get spaceId () {
return this.space.id;
}
clone () {
return new Color(this.space, this.coords, this.alpha);
}
toJSON () {
return {
spaceId: this.spaceId,
coords: this.coords,
alpha: this.alpha,
};
}
display (...args) {
let ret = display(this, ...args);
// Convert color object to Color instance
ret.color = new Color(ret.color);
return ret;
}
/**
* Get a color from the argument(s) passed
* Basically gets us the same result as new Color(color) but doesn't clone an existing color object
*/
static get (color, ...args) {
if (util.isInstance(color, this)) {
return color;
}
return new Color(color, ...args);
}
/**
* Get a color instance from the argument passed or `null` if resolution fails (instead of throwing an error).
* Additionally, it supports passing an element to resolve complex CSS colors through the DOM (slow).
* @see {@link tryColor} for more details
*/
static try (color, options) {
if (util.isInstance(color, this)) {
return color;
}
let ret = tryColor(color, options);
if (ret) {
return new Color(ret);
}
return null;
}
static defineFunction (name, code, o = code) {
let { instance = true, returns } = o;
let func = function (...args) {
let ret = code(...args);
if (returns === "color") {
ret = Color.get(ret);
}
else if (returns === "function<color>") {
let f = ret;
ret = function (...args) {
let ret = f(...args);
return Color.get(ret);
};
// Copy any function metadata
Object.assign(ret, f);
}
else if (returns === "array<color>") {
ret = ret.map(c => Color.get(c));
}
return ret;
};
if (!(name in Color)) {
Color[name] = func;
}
if (instance) {
Color.prototype[name] = function (...args) {
return func(this, ...args);
};
}
}
static defineFunctions (o) {
for (let name in o) {
Color.defineFunction(name, o[name], o[name]);
}
}
static extend (exports) {
if (exports.register) {
exports.register(Color);
}
else {
// No register method, just add the module's functions
for (let name in exports) {
Color.defineFunction(name, exports[name]);
}
}
}
}
Color.defineFunctions({
get,
getAll,
set,
setAll,
to,
equals,
inGamut,
toGamut,
distance,
deltas,
toString: serialize,
});
Object.assign(Color, {
util,
hooks,
WHITES,
Space: ColorSpace,
spaces: ColorSpace.registry,
parse,
// Global defaults one may want to configure
defaults,
});