Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

color-octree

Package Overview
Dependencies
Maintainers
1
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

color-octree - npm Package Compare versions

Comparing version 1.1.1 to 2.0.0

~foo.js

233

dist/cjs.js
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _objectSpread(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
var ownKeys = Object.keys(source);
if (typeof Object.getOwnPropertySymbols === 'function') {
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) {
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
}));
}
ownKeys.forEach(function (key) {
_defineProperty(target, key, source[key]);
});
}
return target;
}
function _slicedToArray(arr, i) {

@@ -7,6 +43,2 @@ return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest();

function _toArray(arr) {
return _arrayWithHoles(arr) || _iterableToArray(arr) || _nonIterableRest();
}
function _arrayWithHoles(arr) {

@@ -16,6 +48,2 @@ if (Array.isArray(arr)) return arr;

function _iterableToArray(iter) {
if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter);
}
function _iterableToArrayLimit(arr, i) {

@@ -53,4 +81,2 @@ var _arr = [];

const dist = (c1, c2) => (c1[0] - c2[0]) ** 2 + (c1[1] - c2[1]) ** 2 + (c1[2] - c2[2]) ** 2;
const hexToRgb = hex => {

@@ -63,96 +89,131 @@ const h = parseHex(hex);

const hexToOctants = hex => {
const h = parseHex(hex);
const distRgb = (c1, c2) => (c1[0] - c2[0]) ** 2 + (c1[1] - c2[1]) ** 2 + (c1[2] - c2[2]) ** 2;
const _Array$from = Array.from({
length: 3
}, (_, i) => parseInt(h.slice(2 * i, 2 * i + 2), 16).toString(2).padStart(8, 0)),
_Array$from2 = _slicedToArray(_Array$from, 3),
r = _Array$from2[0],
g = _Array$from2[1],
b = _Array$from2[2];
const distHex = (a, b) => distRgb(hexToRgb(a), hexToRgb(b));
return Array.from({
length: 8
}, (_, i) => r[i] + g[i] + b[i]);
const rgbToBin = rgb => rgb.map(x => x.toString(2).padStart(8, 0));
const hexToBin = hex => rgbToBin(hexToRgb(hex));
const buildOctree = (depth, n = 0) => n > depth ? undefined : {
n,
colors: [],
'000': buildOctree(depth, n + 1),
'001': buildOctree(depth, n + 1),
'010': buildOctree(depth, n + 1),
'011': buildOctree(depth, n + 1),
'100': buildOctree(depth, n + 1),
'101': buildOctree(depth, n + 1),
'110': buildOctree(depth, n + 1),
'111': buildOctree(depth, n + 1)
};
class ColorOctant {
constructor(path = []) {
this.path = path;
this.colors = [];
let root;
const init = () => {
root = buildOctree(7); // 8 would go down to leaves, but it's too intense for nodejs
};
const add = cols => {
if (!root) init();
if (!path.length) {
this.closest = hex => {
const octants = hexToOctants(hex);
let child = this;
if (Array.isArray(cols)) {
return cols.forEach(c => _add(c)); // might want to precmpute rgb to spped up search
}
for (const octant of octants) {
if (!child[octant] || !child[octant].colors.length) break;
child = child[octant];
}
_add(cols);
};
if (child.colors.length === 1) return child.colors[0];
const targ = hexToRgb(hex);
let maxDist = Infinity,
closest;
child.colors.forEach(col => {
const c = hexToRgb(col.hex);
const d = dist(c, targ);
const _add = col => {
let node = root;
const bin = hexToBin(col.hex);
if (d < maxDist) {
maxDist = d;
closest = col;
}
});
return Object.assign({
d: maxDist
}, closest);
};
}
for (let i = 0; i < 7; i++) {
const k = bin[0][i] + bin[1][i] + bin[2][i];
node = node[k];
node.colors.push(col);
}
};
/**
* get neighbors of a given node
* example: neighbors(['0010', '1101', '0101'], '000') will get the 7 neighbors of this node next the firsr corner ('000')
*/
addAll(colors) {
colors.forEach(({
hex,
name
}) => this.add({
hex,
name,
octants: hexToOctants(hex)
}));
const neighbors = ([r, g, b], dir) => {
const rDec = parseInt(r, 2);
const gDec = parseInt(g, 2);
const bDec = parseInt(b, 2);
const nodes = [];
const n = r.length;
for (let dr = dir[0] - 1; dr <= dir[0]; dr++) {
for (let dg = dir[1] - 1; dg <= dir[1]; dg++) {
for (let db = dir[2] - 1; db <= dir[2]; db++) {
const R = rDec + dr,
G = gDec + dg,
B = bDec + db;
if (R >= 0 && R < 2 ** n && G >= 0 && G < 2 ** n && B >= 0 && B < 2 ** n) {
nodes.push([R.toString(2).padStart(n, 0), G.toString(2).padStart(n, 0), B.toString(2).padStart(n, 0)]);
}
}
}
}
add(color) {
if (this.path.length >= 8 || !this.colors.length) {
this.colors.push(color);
return;
} // else we need to go deeper
// get first octant of each color, and put them in corresponding child for this octant
return nodes;
};
const getNodeFromCoords = ([r, g, b]) => [...r].reduce((node, ri, i) => node[ri + g[i] + b[i]], root);
this.colors.concat(color).forEach((_ref) => {
let hex = _ref.hex,
name = _ref.name,
_ref$octants = _toArray(_ref.octants),
octant = _ref$octants[0],
rest = _ref$octants.slice(1);
const closest = hex => {
const rgb = hexToRgb(hex);
const child = this.child(octant);
child.add({
hex,
name,
octants: rest
});
});
}
const _rgbToBin = rgbToBin(rgb),
_rgbToBin2 = _slicedToArray(_rgbToBin, 3),
r = _rgbToBin2[0],
g = _rgbToBin2[1],
b = _rgbToBin2[2]; // reminder: we don't/can't store level 8
child(octant) {
if (this[octant]) return this[octant];
this[octant] = new ColorOctant(this.path.concat(octant));
return this[octant];
for (let i = 7; i > 0; i--) {
const coords = [r.slice(0, i), g.slice(0, i), b.slice(0, i)]; // take one resolution higher, since we don't/can't store level 8
const ns = neighbors(coords, r[i] + g[i] + b[i]);
const colors = ns.reduce((cs, n) => cs.concat(getNodeFromCoords(n).colors), []);
if (colors.length) {
return closestIn(rgb, colors);
}
} // search in all
const colors = ['000', '001', '010', '011', '100', '101', '110', '111'].reduce((cs, c) => cs.concat(root[c].colors), []);
if (colors.length) {
return closestIn(rgb, colors);
}
}
console.log(`Couldn't find any color`);
};
module.exports = ColorOctant;
const closestIn = (rgb, colors) => {
let minDist = Infinity,
best;
colors.forEach(col => {
const d = distRgb(hexToRgb(col.hex), rgb);
if (d < minDist) {
minDist = d;
best = col;
}
});
return _objectSpread({}, best, {
d: minDist
});
};
exports.parseHex = parseHex;
exports.distHex = distHex;
exports.hexToBin = hexToBin;
exports.init = init;
exports.add = add;
exports.closest = closest;

@@ -0,1 +1,35 @@

function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _objectSpread(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
var ownKeys = Object.keys(source);
if (typeof Object.getOwnPropertySymbols === 'function') {
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) {
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
}));
}
ownKeys.forEach(function (key) {
_defineProperty(target, key, source[key]);
});
}
return target;
}
function _slicedToArray(arr, i) {

@@ -5,6 +39,2 @@ return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest();

function _toArray(arr) {
return _arrayWithHoles(arr) || _iterableToArray(arr) || _nonIterableRest();
}
function _arrayWithHoles(arr) {

@@ -14,6 +44,2 @@ if (Array.isArray(arr)) return arr;

function _iterableToArray(iter) {
if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter);
}
function _iterableToArrayLimit(arr, i) {

@@ -51,4 +77,2 @@ var _arr = [];

const dist = (c1, c2) => (c1[0] - c2[0]) ** 2 + (c1[1] - c2[1]) ** 2 + (c1[2] - c2[2]) ** 2;
const hexToRgb = hex => {

@@ -61,96 +85,126 @@ const h = parseHex(hex);

const hexToOctants = hex => {
const h = parseHex(hex);
const distRgb = (c1, c2) => (c1[0] - c2[0]) ** 2 + (c1[1] - c2[1]) ** 2 + (c1[2] - c2[2]) ** 2;
const _Array$from = Array.from({
length: 3
}, (_, i) => parseInt(h.slice(2 * i, 2 * i + 2), 16).toString(2).padStart(8, 0)),
_Array$from2 = _slicedToArray(_Array$from, 3),
r = _Array$from2[0],
g = _Array$from2[1],
b = _Array$from2[2];
const distHex = (a, b) => distRgb(hexToRgb(a), hexToRgb(b));
return Array.from({
length: 8
}, (_, i) => r[i] + g[i] + b[i]);
const rgbToBin = rgb => rgb.map(x => x.toString(2).padStart(8, 0));
const hexToBin = hex => rgbToBin(hexToRgb(hex));
const buildOctree = (depth, n = 0) => n > depth ? undefined : {
n,
colors: [],
'000': buildOctree(depth, n + 1),
'001': buildOctree(depth, n + 1),
'010': buildOctree(depth, n + 1),
'011': buildOctree(depth, n + 1),
'100': buildOctree(depth, n + 1),
'101': buildOctree(depth, n + 1),
'110': buildOctree(depth, n + 1),
'111': buildOctree(depth, n + 1)
};
class ColorOctant {
constructor(path = []) {
this.path = path;
this.colors = [];
let root;
const init = () => {
root = buildOctree(7); // 8 would go down to leaves, but it's too intense for nodejs
};
const add = cols => {
if (!root) init();
if (!path.length) {
this.closest = hex => {
const octants = hexToOctants(hex);
let child = this;
if (Array.isArray(cols)) {
return cols.forEach(c => _add(c)); // might want to precmpute rgb to spped up search
}
for (const octant of octants) {
if (!child[octant] || !child[octant].colors.length) break;
child = child[octant];
}
_add(cols);
};
if (child.colors.length === 1) return child.colors[0];
const targ = hexToRgb(hex);
let maxDist = Infinity,
closest;
child.colors.forEach(col => {
const c = hexToRgb(col.hex);
const d = dist(c, targ);
const _add = col => {
let node = root;
const bin = hexToBin(col.hex);
if (d < maxDist) {
maxDist = d;
closest = col;
}
});
return Object.assign({
d: maxDist
}, closest);
};
}
for (let i = 0; i < 7; i++) {
const k = bin[0][i] + bin[1][i] + bin[2][i];
node = node[k];
node.colors.push(col);
}
};
/**
* get neighbors of a given node
* example: neighbors(['0010', '1101', '0101'], '000') will get the 7 neighbors of this node next the firsr corner ('000')
*/
addAll(colors) {
colors.forEach(({
hex,
name
}) => this.add({
hex,
name,
octants: hexToOctants(hex)
}));
const neighbors = ([r, g, b], dir) => {
const rDec = parseInt(r, 2);
const gDec = parseInt(g, 2);
const bDec = parseInt(b, 2);
const nodes = [];
const n = r.length;
for (let dr = dir[0] - 1; dr <= dir[0]; dr++) {
for (let dg = dir[1] - 1; dg <= dir[1]; dg++) {
for (let db = dir[2] - 1; db <= dir[2]; db++) {
const R = rDec + dr,
G = gDec + dg,
B = bDec + db;
if (R >= 0 && R < 2 ** n && G >= 0 && G < 2 ** n && B >= 0 && B < 2 ** n) {
nodes.push([R.toString(2).padStart(n, 0), G.toString(2).padStart(n, 0), B.toString(2).padStart(n, 0)]);
}
}
}
}
add(color) {
if (this.path.length >= 8 || !this.colors.length) {
this.colors.push(color);
return;
} // else we need to go deeper
// get first octant of each color, and put them in corresponding child for this octant
return nodes;
};
const getNodeFromCoords = ([r, g, b]) => [...r].reduce((node, ri, i) => node[ri + g[i] + b[i]], root);
this.colors.concat(color).forEach((_ref) => {
let hex = _ref.hex,
name = _ref.name,
_ref$octants = _toArray(_ref.octants),
octant = _ref$octants[0],
rest = _ref$octants.slice(1);
const closest = hex => {
const rgb = hexToRgb(hex);
const child = this.child(octant);
child.add({
hex,
name,
octants: rest
});
});
}
const _rgbToBin = rgbToBin(rgb),
_rgbToBin2 = _slicedToArray(_rgbToBin, 3),
r = _rgbToBin2[0],
g = _rgbToBin2[1],
b = _rgbToBin2[2]; // reminder: we don't/can't store level 8
child(octant) {
if (this[octant]) return this[octant];
this[octant] = new ColorOctant(this.path.concat(octant));
return this[octant];
for (let i = 7; i > 0; i--) {
const coords = [r.slice(0, i), g.slice(0, i), b.slice(0, i)]; // take one resolution higher, since we don't/can't store level 8
const ns = neighbors(coords, r[i] + g[i] + b[i]);
const colors = ns.reduce((cs, n) => cs.concat(getNodeFromCoords(n).colors), []);
if (colors.length) {
return closestIn(rgb, colors);
}
} // search in all
const colors = ['000', '001', '010', '011', '100', '101', '110', '111'].reduce((cs, c) => cs.concat(root[c].colors), []);
if (colors.length) {
return closestIn(rgb, colors);
}
}
console.log(`Couldn't find any color`);
};
export default ColorOctant;
const closestIn = (rgb, colors) => {
let minDist = Infinity,
best;
colors.forEach(col => {
const d = distRgb(hexToRgb(col.hex), rgb);
if (d < minDist) {
minDist = d;
best = col;
}
});
return _objectSpread({}, best, {
d: minDist
});
};
export { parseHex, distHex, hexToBin, init, add, closest };
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.colorutil = factory());
}(this, (function () { 'use strict';
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.colorutil = {})));
}(this, (function (exports) { 'use strict';
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _objectSpread(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
var ownKeys = Object.keys(source);
if (typeof Object.getOwnPropertySymbols === 'function') {
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) {
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
}));
}
ownKeys.forEach(function (key) {
_defineProperty(target, key, source[key]);
});
}
return target;
}
function _slicedToArray(arr, i) {

@@ -11,6 +45,2 @@ return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest();

function _toArray(arr) {
return _arrayWithHoles(arr) || _iterableToArray(arr) || _nonIterableRest();
}
function _arrayWithHoles(arr) {

@@ -20,6 +50,2 @@ if (Array.isArray(arr)) return arr;

function _iterableToArray(iter) {
if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter);
}
function _iterableToArrayLimit(arr, i) {

@@ -57,4 +83,2 @@ var _arr = [];

const dist = (c1, c2) => (c1[0] - c2[0]) ** 2 + (c1[1] - c2[1]) ** 2 + (c1[2] - c2[2]) ** 2;
const hexToRgb = hex => {

@@ -67,98 +91,135 @@ const h = parseHex(hex);

const hexToOctants = hex => {
const h = parseHex(hex);
const distRgb = (c1, c2) => (c1[0] - c2[0]) ** 2 + (c1[1] - c2[1]) ** 2 + (c1[2] - c2[2]) ** 2;
const _Array$from = Array.from({
length: 3
}, (_, i) => parseInt(h.slice(2 * i, 2 * i + 2), 16).toString(2).padStart(8, 0)),
_Array$from2 = _slicedToArray(_Array$from, 3),
r = _Array$from2[0],
g = _Array$from2[1],
b = _Array$from2[2];
const distHex = (a, b) => distRgb(hexToRgb(a), hexToRgb(b));
return Array.from({
length: 8
}, (_, i) => r[i] + g[i] + b[i]);
const rgbToBin = rgb => rgb.map(x => x.toString(2).padStart(8, 0));
const hexToBin = hex => rgbToBin(hexToRgb(hex));
const buildOctree = (depth, n = 0) => n > depth ? undefined : {
n,
colors: [],
'000': buildOctree(depth, n + 1),
'001': buildOctree(depth, n + 1),
'010': buildOctree(depth, n + 1),
'011': buildOctree(depth, n + 1),
'100': buildOctree(depth, n + 1),
'101': buildOctree(depth, n + 1),
'110': buildOctree(depth, n + 1),
'111': buildOctree(depth, n + 1)
};
class ColorOctant {
constructor(path = []) {
this.path = path;
this.colors = [];
let root;
const init = () => {
root = buildOctree(7); // 8 would go down to leaves, but it's too intense for nodejs
};
const add = cols => {
if (!root) init();
if (!path.length) {
this.closest = hex => {
const octants = hexToOctants(hex);
let child = this;
if (Array.isArray(cols)) {
return cols.forEach(c => _add(c)); // might want to precmpute rgb to spped up search
}
for (const octant of octants) {
if (!child[octant] || !child[octant].colors.length) break;
child = child[octant];
}
_add(cols);
};
if (child.colors.length === 1) return child.colors[0];
const targ = hexToRgb(hex);
let maxDist = Infinity,
closest;
child.colors.forEach(col => {
const c = hexToRgb(col.hex);
const d = dist(c, targ);
const _add = col => {
let node = root;
const bin = hexToBin(col.hex);
if (d < maxDist) {
maxDist = d;
closest = col;
}
});
return Object.assign({
d: maxDist
}, closest);
};
}
for (let i = 0; i < 7; i++) {
const k = bin[0][i] + bin[1][i] + bin[2][i];
node = node[k];
node.colors.push(col);
}
};
/**
* get neighbors of a given node
* example: neighbors(['0010', '1101', '0101'], '000') will get the 7 neighbors of this node next the firsr corner ('000')
*/
addAll(colors) {
colors.forEach(({
hex,
name
}) => this.add({
hex,
name,
octants: hexToOctants(hex)
}));
const neighbors = ([r, g, b], dir) => {
const rDec = parseInt(r, 2);
const gDec = parseInt(g, 2);
const bDec = parseInt(b, 2);
const nodes = [];
const n = r.length;
for (let dr = dir[0] - 1; dr <= dir[0]; dr++) {
for (let dg = dir[1] - 1; dg <= dir[1]; dg++) {
for (let db = dir[2] - 1; db <= dir[2]; db++) {
const R = rDec + dr,
G = gDec + dg,
B = bDec + db;
if (R >= 0 && R < 2 ** n && G >= 0 && G < 2 ** n && B >= 0 && B < 2 ** n) {
nodes.push([R.toString(2).padStart(n, 0), G.toString(2).padStart(n, 0), B.toString(2).padStart(n, 0)]);
}
}
}
}
add(color) {
if (this.path.length >= 8 || !this.colors.length) {
this.colors.push(color);
return;
} // else we need to go deeper
// get first octant of each color, and put them in corresponding child for this octant
return nodes;
};
const getNodeFromCoords = ([r, g, b]) => [...r].reduce((node, ri, i) => node[ri + g[i] + b[i]], root);
this.colors.concat(color).forEach((_ref) => {
let hex = _ref.hex,
name = _ref.name,
_ref$octants = _toArray(_ref.octants),
octant = _ref$octants[0],
rest = _ref$octants.slice(1);
const closest = hex => {
const rgb = hexToRgb(hex);
const child = this.child(octant);
child.add({
hex,
name,
octants: rest
});
});
}
const _rgbToBin = rgbToBin(rgb),
_rgbToBin2 = _slicedToArray(_rgbToBin, 3),
r = _rgbToBin2[0],
g = _rgbToBin2[1],
b = _rgbToBin2[2]; // reminder: we don't/can't store level 8
child(octant) {
if (this[octant]) return this[octant];
this[octant] = new ColorOctant(this.path.concat(octant));
return this[octant];
for (let i = 7; i > 0; i--) {
const coords = [r.slice(0, i), g.slice(0, i), b.slice(0, i)]; // take one resolution higher, since we don't/can't store level 8
const ns = neighbors(coords, r[i] + g[i] + b[i]);
const colors = ns.reduce((cs, n) => cs.concat(getNodeFromCoords(n).colors), []);
if (colors.length) {
return closestIn(rgb, colors);
}
} // search in all
const colors = ['000', '001', '010', '011', '100', '101', '110', '111'].reduce((cs, c) => cs.concat(root[c].colors), []);
if (colors.length) {
return closestIn(rgb, colors);
}
}
console.log(`Couldn't find any color`);
};
return ColorOctant;
const closestIn = (rgb, colors) => {
let minDist = Infinity,
best;
colors.forEach(col => {
const d = distRgb(hexToRgb(col.hex), rgb);
if (d < minDist) {
minDist = d;
best = col;
}
});
return _objectSpread({}, best, {
d: minDist
});
};
exports.parseHex = parseHex;
exports.distHex = distHex;
exports.hexToBin = hexToBin;
exports.init = init;
exports.add = add;
exports.closest = closest;
Object.defineProperty(exports, '__esModule', { value: true });
})));

@@ -1,72 +0,123 @@

const parseHex = hex => hex.length < 6 ? hex[hex.length-3].repeat(2)+hex[hex.length-2].repeat(2)+hex[hex.length-1].repeat(2) : hex.slice(-6);
export const parseHex = hex =>
hex.length < 6
? hex[hex.length - 3].repeat(2) + hex[hex.length - 2].repeat(2) + hex[hex.length - 1].repeat(2)
: hex.slice(-6);
const dist = (c1, c2) => (c1[0]-c2[0])**2 + (c1[1]-c2[1])**2 + (c1[2]-c2[2])**2;
const hexToRgb = hex => {
const h = parseHex(hex);
return Array.from({length:3}, (_,i)=>parseInt(h.slice(2*i, 2*i+2), 16));
}
return Array.from({ length: 3 }, (_, i) => parseInt(h.slice(2 * i, 2 * i + 2), 16));
};
const distRgb = (c1, c2) => (c1[0] - c2[0]) ** 2 + (c1[1] - c2[1]) ** 2 + (c1[2] - c2[2]) ** 2;
const hexToOctants = hex => {
const h = parseHex(hex);
const [r,g,b] = Array.from({length:3}, (_,i)=>parseInt(h.slice(2*i, 2*i+2), 16).toString(2).padStart(8,0))
return Array.from({length:8}, (_,i) => r[i]+g[i]+b[i]);
}
export const distHex = (a, b) => distRgb(hexToRgb(a), hexToRgb(b));
const rgbToBin = rgb => rgb.map(x => x.toString(2).padStart(8, 0));
export default class ColorOctant {
constructor(path = []) {
this.path = path;
this.colors = [];
export const hexToBin = hex => rgbToBin(hexToRgb(hex));
if (!path.length) {
this.closest = hex => {
const octants = hexToOctants(hex);
let child = this;
for (const octant of octants) {
if (!child[octant] || !child[octant].colors.length) break;
child = child[octant]
}
const buildOctree = (depth, n = 0) =>
n > depth
? undefined
: {
n,
colors: [],
'000': buildOctree(depth, n + 1),
'001': buildOctree(depth, n + 1),
'010': buildOctree(depth, n + 1),
'011': buildOctree(depth, n + 1),
'100': buildOctree(depth, n + 1),
'101': buildOctree(depth, n + 1),
'110': buildOctree(depth, n + 1),
'111': buildOctree(depth, n + 1),
};
if (child.colors.length === 1) return child.colors[0];
const targ = hexToRgb(hex)
let maxDist = Infinity, closest;
child.colors.forEach(col => {
const c = hexToRgb(col.hex);
const d = dist(c, targ);
if (d < maxDist) {
maxDist = d;
closest = col;
}
});
return Object.assign({d: maxDist}, closest);
}
}
let root;
export const init = () => {
root = buildOctree(7); // 8 would go down to leaves, but it's too intense for nodejs
};
export const add = cols => {
if (!root) init();
if (Array.isArray(cols)) {
return cols.forEach(c => _add(c)); // might want to precmpute rgb to spped up search
}
_add(cols);
};
addAll(colors) {
colors.forEach(({hex, name}) => this.add({hex, name, octants: hexToOctants(hex)}));
const _add = col => {
let node = root;
const bin = hexToBin(col.hex);
for (let i = 0; i < 7; i++) {
const k = bin[0][i] + bin[1][i] + bin[2][i];
node = node[k];
node.colors.push(col);
}
};
add(color) {
if (this.path.length >= 8 || !this.colors.length) {
this.colors.push(color);
return;
/**
* get neighbors of a given node
* example: neighbors(['0010', '1101', '0101'], '000') will get the 7 neighbors of this node next the firsr corner ('000')
*/
const neighbors = ([r, g, b], dir) => {
const rDec = parseInt(r, 2);
const gDec = parseInt(g, 2);
const bDec = parseInt(b, 2);
const nodes = [];
const n = r.length;
for (let dr = dir[0] - 1; dr <= dir[0]; dr++) {
for (let dg = dir[1] - 1; dg <= dir[1]; dg++) {
for (let db = dir[2] - 1; db <= dir[2]; db++) {
const R = rDec + dr,
G = gDec + dg,
B = bDec + db;
if (R >= 0 && R < 2 ** n && G >= 0 && G < 2 ** n && B >= 0 && B < 2 ** n) {
nodes.push([
R.toString(2).padStart(n, 0),
G.toString(2).padStart(n, 0),
B.toString(2).padStart(n, 0),
]);
}
}
}
// else we need to go deeper
// get first octant of each color, and put them in corresponding child for this octant
this.colors.concat(color).forEach(({hex, name, octants: [octant, ...rest]}) => {
const child = this.child(octant);
child.add({hex, name, octants: rest});
});
}
return nodes;
};
child(octant) {
if (this[octant]) return this[octant];
this[octant] = new ColorOctant(this.path.concat(octant));
return this[octant];
const getNodeFromCoords = ([r, g, b]) => [...r].reduce((node, ri, i) => node[ri + g[i] + b[i]], root);
export const closest = hex => {
const rgb = hexToRgb(hex);
const [r, g, b] = rgbToBin(rgb);
// reminder: we don't/can't store level 8
for (let i = 7; i > 0; i--) {
const coords = [r.slice(0, i), g.slice(0, i), b.slice(0, i)]; // take one resolution higher, since we don't/can't store level 8
const ns = neighbors(coords, r[i] + g[i] + b[i]);
const colors = ns.reduce((cs, n) => cs.concat(getNodeFromCoords(n).colors), []);
if (colors.length) {
return closestIn(rgb, colors);
}
}
}
// search in all
const colors = ['000', '001', '010', '011', '100', '101', '110', '111'].reduce(
(cs, c) => cs.concat(root[c].colors),
[],
);
if (colors.length) {
return closestIn(rgb, colors);
}
console.log(`Couldn't find any color`);
};
const closestIn = (rgb, colors) => {
let minDist = Infinity,
best;
colors.forEach(col => {
const d = distRgb(hexToRgb(col.hex), rgb);
if (d < minDist) {
minDist = d;
best = col;
}
});
return { ...best, d: minDist };
};
{
"name": "color-octree",
"version": "1.1.1",
"version": "2.0.0",
"description": "Get closest hex color",

@@ -5,0 +5,0 @@ "main": "dist/cjs.js",

## Find efficiently the closest hex color
```js
import colorNames from 'color-names';
import ColorTree from 'color-octree';
import colorNames from 'color-names';
import { add, closest } from 'color-octree';
// we expect an array of {hex, name}
const entries = Object.entries(colorNames).map(([hex, name]) => ({hex, name}));
const colors = Object.entries(colorNames).map(([hex, name]) => ({ hex, name }));
const tree = new ColorTree();
tree.addAll(entries);
console.log(tree.closest('5544df'))
add(colors);
console.log(closest('5544df'));
```
[live example](https://repl.it/@caub/closest-color)
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc