color-octree
Advanced tools
Comparing version 2.0.2 to 2.1.0
@@ -79,8 +79,3 @@ 'use strict'; | ||
const hexToRgb = hex => { | ||
const h = parseHex(hex); | ||
return Array.from({ | ||
length: 3 | ||
}, (_, i) => parseInt(h.slice(2 * i, 2 * i + 2), 16)); | ||
}; | ||
const hexToRgb = hex => [parseInt(hex.slice(0, 2), 16), parseInt(hex.slice(2, 4), 16), parseInt(hex.slice(4, 6), 16)]; | ||
@@ -107,12 +102,21 @@ const distRgb = (c1, c2) => (c1[0] - c2[0]) ** 2 + (c1[1] - c2[1]) ** 2 + (c1[2] - c2[2]) ** 2; | ||
}; | ||
/** | ||
* init __root | ||
* @param {*} depth | ||
*/ | ||
let root; | ||
const init = () => { | ||
root = buildOctree(7); // 8 would go down to leaves, but it's too intense for nodejs | ||
const init = (depth = 7) => { | ||
if (!(depth >= 0 && depth < 8)) throw new Error('depth must be between 0 and 7'); // 8 would go down to leaves, but it's too intense for nodejs | ||
exports.__root = {}; | ||
exports.__root = _objectSpread({}, buildOctree(depth), { | ||
depth, | ||
colors: undefined | ||
}); // colors are not stored at top-level | ||
}; | ||
const add = cols => { | ||
if (!root) init(); | ||
const add = (cols, depth) => { | ||
if (!exports.__root) init(depth); | ||
if (Array.isArray(cols)) { | ||
return cols.forEach(c => _add(c)); // might want to precmpute rgb to spped up search | ||
return cols.forEach(c => _add(c)); // might want to precompute rgb to speed up search | ||
} | ||
@@ -124,6 +128,6 @@ | ||
const _add = col => { | ||
let node = root; | ||
const bin = hexToBin(col.hex); | ||
let node = exports.__root; | ||
const bin = hexToBin(parseHex(col.hex)); | ||
for (let i = 0; i < 7; i++) { | ||
for (let i = 0; i < exports.__root.depth; i++) { | ||
const k = bin[0][i] + bin[1][i] + bin[2][i]; | ||
@@ -134,2 +138,17 @@ node = node[k]; | ||
}; | ||
const remove = hex => { | ||
let node = exports.__root; | ||
const bin = hexToBin(parseHex(hex)); | ||
for (let i = 0; i < exports.__root.depth; i++) { | ||
const k = bin[0][i] + bin[1][i] + bin[2][i]; | ||
node = node[k]; | ||
const idx = node.colors.findIndex(c => c.hex === hex); | ||
if (idx >= 0) { | ||
node.colors = [...node.colors.slice(0, idx), ...node.colors.slice(idx + 1)]; // don't like splice | ||
} | ||
} | ||
}; | ||
/** | ||
@@ -140,3 +159,2 @@ * get neighbors of a given node | ||
const neighbors = ([r, g, b], dir) => { | ||
@@ -166,6 +184,14 @@ const rDec = parseInt(r, 2); | ||
const getNodeFromCoords = ([r, g, b]) => [...r].reduce((node, ri, i) => node[ri + g[i] + b[i]], root); | ||
const getNodeFromCoords = ([r, g, b]) => { | ||
let node = exports.__root; | ||
for (let i = 0; i < Math.min(exports.__root.depth, r.length); i++) { | ||
node = node[r[i] + g[i] + b[i]]; | ||
} | ||
return node; | ||
}; | ||
const closest = hex => { | ||
const rgb = hexToRgb(hex); | ||
const rgb = hexToRgb(parseHex(hex)); | ||
@@ -179,7 +205,7 @@ const _rgbToBin = rgbToBin(rgb), | ||
for (let i = 7; i > 0; i--) { | ||
for (let i = exports.__root.depth; 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), []); | ||
const colors = ns.map(n => getNodeFromCoords(n).colors).reduce((cs, c) => cs.concat(c), []); | ||
@@ -192,3 +218,3 @@ if (colors.length) { | ||
const colors = ['000', '001', '010', '011', '100', '101', '110', '111'].reduce((cs, c) => cs.concat(root[c].colors), []); | ||
const colors = ['000', '001', '010', '011', '100', '101', '110', '111'].map(node => exports.__root[node].colors).reduce((cs, c) => cs.concat(c), []); | ||
@@ -206,3 +232,3 @@ if (colors.length) { | ||
colors.forEach(col => { | ||
const d = distRgb(hexToRgb(col.hex), rgb); | ||
const d = distRgb(hexToRgb(parseHex(col.hex)), rgb); | ||
@@ -224,2 +250,3 @@ if (d < minDist) { | ||
exports.add = add; | ||
exports.remove = remove; | ||
exports.closest = closest; |
@@ -75,8 +75,3 @@ function _defineProperty(obj, key, value) { | ||
const hexToRgb = hex => { | ||
const h = parseHex(hex); | ||
return Array.from({ | ||
length: 3 | ||
}, (_, i) => parseInt(h.slice(2 * i, 2 * i + 2), 16)); | ||
}; | ||
const hexToRgb = hex => [parseInt(hex.slice(0, 2), 16), parseInt(hex.slice(2, 4), 16), parseInt(hex.slice(4, 6), 16)]; | ||
@@ -104,11 +99,22 @@ const distRgb = (c1, c2) => (c1[0] - c2[0]) ** 2 + (c1[1] - c2[1]) ** 2 + (c1[2] - c2[2]) ** 2; | ||
let root; | ||
const init = () => { | ||
root = buildOctree(7); // 8 would go down to leaves, but it's too intense for nodejs | ||
let __root; | ||
/** | ||
* init __root | ||
* @param {*} depth | ||
*/ | ||
const init = (depth = 7) => { | ||
if (!(depth >= 0 && depth < 8)) throw new Error('depth must be between 0 and 7'); // 8 would go down to leaves, but it's too intense for nodejs | ||
__root = {}; | ||
__root = _objectSpread({}, buildOctree(depth), { | ||
depth, | ||
colors: undefined | ||
}); // colors are not stored at top-level | ||
}; | ||
const add = cols => { | ||
if (!root) init(); | ||
const add = (cols, depth) => { | ||
if (!__root) init(depth); | ||
if (Array.isArray(cols)) { | ||
return cols.forEach(c => _add(c)); // might want to precmpute rgb to spped up search | ||
return cols.forEach(c => _add(c)); // might want to precompute rgb to speed up search | ||
} | ||
@@ -120,6 +126,6 @@ | ||
const _add = col => { | ||
let node = root; | ||
const bin = hexToBin(col.hex); | ||
let node = __root; | ||
const bin = hexToBin(parseHex(col.hex)); | ||
for (let i = 0; i < 7; i++) { | ||
for (let i = 0; i < __root.depth; i++) { | ||
const k = bin[0][i] + bin[1][i] + bin[2][i]; | ||
@@ -130,2 +136,17 @@ node = node[k]; | ||
}; | ||
const remove = hex => { | ||
let node = __root; | ||
const bin = hexToBin(parseHex(hex)); | ||
for (let i = 0; i < __root.depth; i++) { | ||
const k = bin[0][i] + bin[1][i] + bin[2][i]; | ||
node = node[k]; | ||
const idx = node.colors.findIndex(c => c.hex === hex); | ||
if (idx >= 0) { | ||
node.colors = [...node.colors.slice(0, idx), ...node.colors.slice(idx + 1)]; // don't like splice | ||
} | ||
} | ||
}; | ||
/** | ||
@@ -136,3 +157,2 @@ * get neighbors of a given node | ||
const neighbors = ([r, g, b], dir) => { | ||
@@ -162,6 +182,14 @@ const rDec = parseInt(r, 2); | ||
const getNodeFromCoords = ([r, g, b]) => [...r].reduce((node, ri, i) => node[ri + g[i] + b[i]], root); | ||
const getNodeFromCoords = ([r, g, b]) => { | ||
let node = __root; | ||
for (let i = 0; i < Math.min(__root.depth, r.length); i++) { | ||
node = node[r[i] + g[i] + b[i]]; | ||
} | ||
return node; | ||
}; | ||
const closest = hex => { | ||
const rgb = hexToRgb(hex); | ||
const rgb = hexToRgb(parseHex(hex)); | ||
@@ -175,7 +203,7 @@ const _rgbToBin = rgbToBin(rgb), | ||
for (let i = 7; i > 0; i--) { | ||
for (let i = __root.depth; 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), []); | ||
const colors = ns.map(n => getNodeFromCoords(n).colors).reduce((cs, c) => cs.concat(c), []); | ||
@@ -188,3 +216,3 @@ if (colors.length) { | ||
const colors = ['000', '001', '010', '011', '100', '101', '110', '111'].reduce((cs, c) => cs.concat(root[c].colors), []); | ||
const colors = ['000', '001', '010', '011', '100', '101', '110', '111'].map(node => __root[node].colors).reduce((cs, c) => cs.concat(c), []); | ||
@@ -202,3 +230,3 @@ if (colors.length) { | ||
colors.forEach(col => { | ||
const d = distRgb(hexToRgb(col.hex), rgb); | ||
const d = distRgb(hexToRgb(parseHex(col.hex)), rgb); | ||
@@ -215,2 +243,2 @@ if (d < minDist) { | ||
export { parseHex, distHex, hexToBin, init, add, closest }; | ||
export { parseHex, distHex, hexToBin, __root, init, add, remove, closest }; |
@@ -81,8 +81,3 @@ (function (global, factory) { | ||
const hexToRgb = hex => { | ||
const h = parseHex(hex); | ||
return Array.from({ | ||
length: 3 | ||
}, (_, i) => parseInt(h.slice(2 * i, 2 * i + 2), 16)); | ||
}; | ||
const hexToRgb = hex => [parseInt(hex.slice(0, 2), 16), parseInt(hex.slice(2, 4), 16), parseInt(hex.slice(4, 6), 16)]; | ||
@@ -109,12 +104,21 @@ const distRgb = (c1, c2) => (c1[0] - c2[0]) ** 2 + (c1[1] - c2[1]) ** 2 + (c1[2] - c2[2]) ** 2; | ||
}; | ||
/** | ||
* init __root | ||
* @param {*} depth | ||
*/ | ||
let root; | ||
const init = () => { | ||
root = buildOctree(7); // 8 would go down to leaves, but it's too intense for nodejs | ||
const init = (depth = 7) => { | ||
if (!(depth >= 0 && depth < 8)) throw new Error('depth must be between 0 and 7'); // 8 would go down to leaves, but it's too intense for nodejs | ||
exports.__root = {}; | ||
exports.__root = _objectSpread({}, buildOctree(depth), { | ||
depth, | ||
colors: undefined | ||
}); // colors are not stored at top-level | ||
}; | ||
const add = cols => { | ||
if (!root) init(); | ||
const add = (cols, depth) => { | ||
if (!exports.__root) init(depth); | ||
if (Array.isArray(cols)) { | ||
return cols.forEach(c => _add(c)); // might want to precmpute rgb to spped up search | ||
return cols.forEach(c => _add(c)); // might want to precompute rgb to speed up search | ||
} | ||
@@ -126,6 +130,6 @@ | ||
const _add = col => { | ||
let node = root; | ||
const bin = hexToBin(col.hex); | ||
let node = exports.__root; | ||
const bin = hexToBin(parseHex(col.hex)); | ||
for (let i = 0; i < 7; i++) { | ||
for (let i = 0; i < exports.__root.depth; i++) { | ||
const k = bin[0][i] + bin[1][i] + bin[2][i]; | ||
@@ -136,2 +140,17 @@ node = node[k]; | ||
}; | ||
const remove = hex => { | ||
let node = exports.__root; | ||
const bin = hexToBin(parseHex(hex)); | ||
for (let i = 0; i < exports.__root.depth; i++) { | ||
const k = bin[0][i] + bin[1][i] + bin[2][i]; | ||
node = node[k]; | ||
const idx = node.colors.findIndex(c => c.hex === hex); | ||
if (idx >= 0) { | ||
node.colors = [...node.colors.slice(0, idx), ...node.colors.slice(idx + 1)]; // don't like splice | ||
} | ||
} | ||
}; | ||
/** | ||
@@ -142,3 +161,2 @@ * get neighbors of a given node | ||
const neighbors = ([r, g, b], dir) => { | ||
@@ -168,6 +186,14 @@ const rDec = parseInt(r, 2); | ||
const getNodeFromCoords = ([r, g, b]) => [...r].reduce((node, ri, i) => node[ri + g[i] + b[i]], root); | ||
const getNodeFromCoords = ([r, g, b]) => { | ||
let node = exports.__root; | ||
for (let i = 0; i < Math.min(exports.__root.depth, r.length); i++) { | ||
node = node[r[i] + g[i] + b[i]]; | ||
} | ||
return node; | ||
}; | ||
const closest = hex => { | ||
const rgb = hexToRgb(hex); | ||
const rgb = hexToRgb(parseHex(hex)); | ||
@@ -181,7 +207,7 @@ const _rgbToBin = rgbToBin(rgb), | ||
for (let i = 7; i > 0; i--) { | ||
for (let i = exports.__root.depth; 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), []); | ||
const colors = ns.map(n => getNodeFromCoords(n).colors).reduce((cs, c) => cs.concat(c), []); | ||
@@ -194,3 +220,3 @@ if (colors.length) { | ||
const colors = ['000', '001', '010', '011', '100', '101', '110', '111'].reduce((cs, c) => cs.concat(root[c].colors), []); | ||
const colors = ['000', '001', '010', '011', '100', '101', '110', '111'].map(node => exports.__root[node].colors).reduce((cs, c) => cs.concat(c), []); | ||
@@ -208,3 +234,3 @@ if (colors.length) { | ||
colors.forEach(col => { | ||
const d = distRgb(hexToRgb(col.hex), rgb); | ||
const d = distRgb(hexToRgb(parseHex(col.hex)), rgb); | ||
@@ -226,2 +252,3 @@ if (d < minDist) { | ||
exports.add = add; | ||
exports.remove = remove; | ||
exports.closest = closest; | ||
@@ -228,0 +255,0 @@ |
72
index.js
@@ -6,6 +6,8 @@ export const parseHex = hex => | ||
const hexToRgb = hex => { | ||
const h = parseHex(hex); | ||
return Array.from({ length: 3 }, (_, i) => parseInt(h.slice(2 * i, 2 * i + 2), 16)); | ||
}; | ||
const hexToRgb = hex => [ | ||
parseInt(hex.slice(0, 2), 16), | ||
parseInt(hex.slice(2, 4), 16), | ||
parseInt(hex.slice(4, 6), 16) | ||
]; | ||
const distRgb = (c1, c2) => (c1[0] - c2[0]) ** 2 + (c1[1] - c2[1]) ** 2 + (c1[2] - c2[2]) ** 2; | ||
@@ -35,12 +37,19 @@ | ||
let root; | ||
export let __root; | ||
export const init = () => { | ||
root = buildOctree(7); // 8 would go down to leaves, but it's too intense for nodejs | ||
/** | ||
* init __root | ||
* @param {*} depth | ||
*/ | ||
export const init = (depth = 7) => { | ||
if (!(depth >= 0 && depth < 8)) throw new Error('depth must be between 0 and 7'); | ||
// 8 would go down to leaves, but it's too intense for nodejs | ||
__root = {}; | ||
__root = {...buildOctree(depth), depth, colors: undefined}; // colors are not stored at top-level | ||
}; | ||
export const add = cols => { | ||
if (!root) init(); | ||
export const add = (cols, depth) => { | ||
if (!__root) init(depth); | ||
if (Array.isArray(cols)) { | ||
return cols.forEach(c => _add(c)); // might want to precmpute rgb to spped up search | ||
return cols.forEach(c => _add(c)); // might want to precompute rgb to speed up search | ||
} | ||
@@ -51,5 +60,5 @@ _add(cols); | ||
const _add = col => { | ||
let node = root; | ||
const bin = hexToBin(col.hex); | ||
for (let i = 0; i < 7; i++) { | ||
let node = __root; | ||
const bin = hexToBin(parseHex(col.hex)); | ||
for (let i = 0; i < __root.depth; i++) { | ||
const k = bin[0][i] + bin[1][i] + bin[2][i]; | ||
@@ -61,2 +70,15 @@ node = node[k]; | ||
export const remove = hex => { | ||
let node = __root; | ||
const bin = hexToBin(parseHex(hex)); | ||
for (let i = 0; i < __root.depth; i++) { | ||
const k = bin[0][i] + bin[1][i] + bin[2][i]; | ||
node = node[k]; | ||
const idx = node.colors.findIndex(c => c.hex === hex); | ||
if (idx >= 0) { | ||
node.colors = [...node.colors.slice(0,idx), ...node.colors.slice(idx+1)]; // don't like splice | ||
} | ||
} | ||
} | ||
/** | ||
@@ -91,12 +113,18 @@ * get neighbors of a given node | ||
const getNodeFromCoords = ([r, g, b]) => [...r].reduce((node, ri, i) => node[ri + g[i] + b[i]], root); | ||
const getNodeFromCoords = ([r, g, b]) => { | ||
let node = __root; | ||
for (let i = 0; i < Math.min(__root.depth, r.length); i++) { | ||
node = node[r[i] + g[i] + b[i]]; | ||
} | ||
return node; | ||
}; | ||
export const closest = hex => { | ||
const rgb = hexToRgb(hex); | ||
const rgb = hexToRgb(parseHex(hex)); | ||
const [r, g, b] = rgbToBin(rgb); | ||
// reminder: we don't/can't store level 8 | ||
for (let i = 7; i > 0; i--) { | ||
for (let i = __root.depth; 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), []); | ||
const colors = ns.map(n => getNodeFromCoords(n).colors).reduce((cs, c) => cs.concat(c), []); | ||
if (colors.length) { | ||
@@ -107,6 +135,6 @@ 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), | ||
[], | ||
); | ||
const colors = ['000', '001', '010', '011', '100', '101', '110', '111'] | ||
.map(node => __root[node].colors) | ||
.reduce((cs, c) => cs.concat(c), []); | ||
if (colors.length) { | ||
@@ -122,3 +150,3 @@ return closestIn(rgb, colors); | ||
colors.forEach(col => { | ||
const d = distRgb(hexToRgb(col.hex), rgb); | ||
const d = distRgb(hexToRgb(parseHex(col.hex)), rgb); | ||
if (d < minDist) { | ||
@@ -125,0 +153,0 @@ minDist = d; |
{ | ||
"name": "color-octree", | ||
"version": "2.0.2", | ||
"version": "2.1.0", | ||
"description": "Get closest hex color", | ||
@@ -9,4 +9,4 @@ "main": "dist/cjs.js", | ||
"scripts": { | ||
"test": "node test", | ||
"prepare": "npx rollup -c" | ||
"test": "babel-node index.spec", | ||
"prepare": "NODE_ENV=rollup npx rollup -c" | ||
}, | ||
@@ -32,2 +32,3 @@ "repository": { | ||
"@babel/core": "^7.0.0-beta.44", | ||
"@babel/node": "^7.0.0-beta.44", | ||
"@babel/preset-env": "^7.0.0-beta.44", | ||
@@ -34,0 +35,0 @@ "color-name-list": "^3.10.0", |
## Find efficiently the closest hex color | ||
### API | ||
- `add(colors /*array of {hex, name}*/)`: add an array of colors (`add` calls `init` if it was not initialized yet) | ||
- `closest(hex)`: Search for the closest color | ||
- `init(depth: Int = 7)`: Init the tree at a given depth (default 7), accepted range: [0, 7] | ||
- `remove(hex)`: Remove a a color object by its hex property | ||
```js | ||
@@ -15,1 +22,6 @@ import colorNames from 'color-names'; | ||
[live example](https://repl.it/@caub/closest-color) | ||
### Notice | ||
It uses `String.prototype.padStart`, it exist on node 8.11 and recent browsers, but you might still want to polyfill it (see polyfill.io or es-shims) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
27626
8
774
27
8