UNPKG

@thi.ng/color

Version:

Array-based color types, CSS parsing, conversions, transformations, declarative theme generation, gradients, presets

119 lines (118 loc) 4.12 kB
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 };