@thi.ng/color
Version:
Array-based color types, CSS parsing, conversions, transformations, declarative theme generation, gradients, presets
119 lines (118 loc) • 4.12 kB
JavaScript
import { implementsFunction } from "@thi.ng/checks/implements-function";
import { isArrayLike } from "@thi.ng/checks/is-arraylike";
import { isNumber } from "@thi.ng/checks/is-number";
import { isString } from "@thi.ng/checks/is-string";
import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
import { EPS } from "@thi.ng/math/api";
import { fract } from "@thi.ng/math/prec";
import { vector } from "@thi.ng/strings/vector";
import { mapStridedBuffer } from "@thi.ng/vectors/buffer";
import { clamp4 } from "@thi.ng/vectors/clamp";
import { declareIndices } from "@thi.ng/vectors/accessors";
import { eqDelta4 } from "@thi.ng/vectors/eqdelta";
import { stridedValues } from "@thi.ng/vectors/iterator";
import { randMinMax } from "@thi.ng/vectors/rand-minmax";
import { set4 } from "@thi.ng/vectors/set";
import { convert, defConversions } from "./convert.js";
import { parseCss } from "./css/parse-css.js";
import { intArgb32Srgb } from "./int/int-srgb.js";
import { __ensureArgs } from "./internal/ensure.js";
const defColor = (spec) => {
const channels = spec.channels || {};
const order = spec.order;
const numChannels = order.length;
order.reduce((acc, id) => {
acc[id] = {
range: [0, 1],
...channels[id]
};
return acc;
}, channels);
const min = Object.freeze(order.map((id) => channels[id].range[0]));
const max = Object.freeze(order.map((id) => channels[id].range[1]));
const minR = set4([], min);
const maxR = set4([], max);
minR[numChannels - 1] = 1;
const hueChanID = order.findIndex((id) => !!channels[id].hue);
const $Color = class {
constructor(buf, offset = 0, stride = 1) {
this.offset = offset;
this.stride = stride;
this.buf = buf || [0, 0, 0, 0];
this.offset = offset;
this.stride = stride;
}
buf;
get mode() {
return spec.mode;
}
get length() {
return numChannels;
}
get range() {
return [min, max];
}
get [Symbol.toStringTag]() {
return spec.mode;
}
get xyz() {
return [this[0], this[1], this[2]];
}
[Symbol.iterator]() {
return stridedValues(
this.buf,
this.length,
this.offset,
this.stride
);
}
copy() {
return new $Color(this.deref());
}
copyView() {
return new $Color(this.buf, this.offset, this.stride);
}
empty() {
return new $Color();
}
deref() {
return [this[0], this[1], this[2], this[3]];
}
set(src) {
return set4(this, src);
}
clamp() {
hueChanID >= 0 && (this[hueChanID] = fract(this[hueChanID]));
clamp4(null, this, min, max);
return this;
}
eqDelta(o, eps = EPS) {
return eqDelta4(this, o, eps);
}
randomize(rnd) {
return randMinMax(this, minR, maxR, rnd);
}
toJSON() {
return this.deref();
}
toString() {
return vector(4, 4)(this);
}
};
declareIndices($Color.prototype, order);
defConversions(spec.mode, spec.from);
defConversions("rgb", { [spec.mode]: spec.toRgb });
const fromColor = (src, mode, args) => {
const res = new $Color(...args);
return mode !== spec.mode ? convert(res, src, spec.mode, mode) : res.set(src);
};
const factory = (src, ...args) => src == null ? new $Color() : isString(src) ? factory(parseCss(src), ...args) : isArrayLike(src) ? isString(src.mode) ? fromColor(src, src.mode, args) : new $Color(src, ...args) : implementsFunction(src, "deref") ? fromColor(src.deref(), src.mode, args) : isNumber(src) ? args.length && args.every(isNumber) ? new $Color(...__ensureArgs([src, ...args])) : fromColor(intArgb32Srgb([], src), "srgb", args) : illegalArgs(`can't create a ${spec.mode} color from: ${src}`);
factory.class = $Color;
factory.range = [min, max];
factory.random = (rnd, buf, idx, stride) => new $Color(buf, idx, stride).randomize(rnd);
factory.mapBuffer = (buf, num = buf.length / numChannels | 0, start = 0, cstride = 1, estride = numChannels) => mapStridedBuffer($Color, buf, num, start, cstride, estride);
return factory;
};
export {
defColor
};