extract-colors
Advanced tools
Comparing version 2.0.3 to 2.0.4
@@ -9,9 +9,9 @@ import Color from './Color'; | ||
export default class BudGroup { | ||
count: number; | ||
children: { | ||
_count: number; | ||
_children: { | ||
[key: number]: Color; | ||
}; | ||
maxWeight: number | undefined; | ||
_maxWeight: number | undefined; | ||
/** | ||
* Store colors or groups and count similiar groups in the image. | ||
* Store colors or groups and _count similiar groups in the image. | ||
*/ | ||
@@ -22,3 +22,3 @@ constructor(); | ||
*/ | ||
addColor(hex: number, red: number, green: number, blue: number): Color; | ||
addColor(_hex: number, _red: number, _green: number, _blue: number): Color; | ||
/** | ||
@@ -29,13 +29,13 @@ * Get list of groups of list of colors. | ||
/** | ||
* Max color weight between the children colors, depends of his saturation and his count. | ||
* Max color weight between the list colors, depends of his saturation and his _count. | ||
*/ | ||
getMaxWeight(count: number): number; | ||
getMaxWeight(_count: number): number; | ||
/** | ||
* Color with the the max weight between the children colors, depends of his saturation and his count. | ||
* Color with the the max weight between the list colors, depends of his saturation and his _count. | ||
*/ | ||
getMaxWeightColor(count: number): Color; | ||
getMaxWeightColor(_count: number): Color; | ||
/** | ||
* Max count of colors for a group of colors. | ||
* Max _count of colors for a group of colors. | ||
*/ | ||
getMaxCountColor(): Color; | ||
} |
@@ -8,11 +8,11 @@ /** | ||
export default class Color { | ||
red: number; | ||
green: number; | ||
blue: number; | ||
hex: number; | ||
count: number; | ||
private _saturation; | ||
private _hue; | ||
private _lightness; | ||
private _intensity; | ||
_red: number; | ||
_green: number; | ||
_blue: number; | ||
_hex: number; | ||
_count: number; | ||
private __saturation; | ||
private __hue; | ||
private __lightness; | ||
private __intensity; | ||
/** | ||
@@ -32,15 +32,15 @@ * Set red, green and blue colors to create the Color object. | ||
*/ | ||
get hue(): number; | ||
get _hue(): number; | ||
/** | ||
* Saturation from 0 to 1 | ||
*/ | ||
get saturation(): number; | ||
get _saturation(): number; | ||
/** | ||
* Lightness from 0 to 1 | ||
*/ | ||
get lightness(): number; | ||
get _lightness(): number; | ||
/** | ||
* Color intensity from 0 to 1 | ||
*/ | ||
get intensity(): number; | ||
get _intensity(): number; | ||
} |
@@ -10,10 +10,9 @@ import Color from './Color'; | ||
export default class RootGroup { | ||
isColor: boolean; | ||
count: number; | ||
children: { | ||
_count: number; | ||
_children: { | ||
[key: number]: RootGroup | BudGroup; | ||
}; | ||
maxWeight: number | undefined; | ||
_maxWeight: number | undefined; | ||
/** | ||
* Store colors or groups and count similiar groups in the image. | ||
* Store colors or groups and _count similiar groups in the image. | ||
*/ | ||
@@ -36,11 +35,11 @@ constructor(); | ||
/** | ||
* Max color weight between the children colors, depends of his saturation and his count. | ||
* Max color weight between the list colors, depends of his saturation and his _count. | ||
*/ | ||
getMaxWeight(count: number): number; | ||
getMaxWeight(_count: number): number; | ||
/** | ||
* Color with the the max weight between the children colors, depends of his saturation and his count. | ||
* Color with the the max weight between the list colors, depends of his saturation and his _count. | ||
*/ | ||
getMaxWeightColor(count: number): Color; | ||
getMaxWeightColor(_count: number): Color; | ||
/** | ||
* Max count of colors for a group of colors. | ||
* Max _count of colors for a group of colors. | ||
*/ | ||
@@ -50,5 +49,5 @@ getMaxCountColor(): Color; | ||
* List of colors sorted by importance (neighboring hare calculated by distance and removed). | ||
* Importance is calculated with the saturation and count of neighboring colors. | ||
* Importance is calculated with the saturation and _count of neighboring colors. | ||
*/ | ||
getColors(distance: number, count: number): Color[]; | ||
getColors(_distance: number, _count: number): Color[]; | ||
} |
@@ -0,12 +1,5 @@ | ||
import { extractColorsFromImageData } from "./extractColors"; | ||
import { FinalColor } from "./types/Color"; | ||
import type { BrowserOptions, NodeOptions } from "./types/Options"; | ||
import type { BrowserOptions } from "./types/Options"; | ||
/** | ||
* Extract colors from an ImageData object. | ||
*/ | ||
declare const extractColorsFromImageData: (imageData: ImageData | { | ||
data: Uint8ClampedArray | number[]; | ||
width?: number; | ||
height?: number; | ||
}, options?: NodeOptions) => FinalColor[]; | ||
/** | ||
* Extract colors from an HTMLImageElement. | ||
@@ -13,0 +6,0 @@ */ |
@@ -1,53 +0,40 @@ | ||
const createFinalColor = (color, pixels) => { | ||
return { | ||
hex: `#${"0".repeat(6 - color.hex.toString(16).length)}${color.hex.toString(16)}`, | ||
red: color.red, | ||
green: color.green, | ||
blue: color.blue, | ||
area: color.count / pixels, | ||
hue: color.hue, | ||
saturation: color.saturation, | ||
lightness: color.lightness, | ||
intensity: color.intensity | ||
}; | ||
}; | ||
class Color { | ||
constructor(red, green, blue, hex = red << 16 | green << 8 | blue) { | ||
this.count = 1; | ||
this._saturation = -1; | ||
this._hue = -1; | ||
this._lightness = -1; | ||
this._intensity = -1; | ||
this.red = red; | ||
this.green = green; | ||
this.blue = blue; | ||
this.hex = hex; | ||
this._count = 1; | ||
this.__saturation = -1; | ||
this.__hue = -1; | ||
this.__lightness = -1; | ||
this.__intensity = -1; | ||
this._red = red; | ||
this._green = green; | ||
this._blue = blue; | ||
this._hex = hex; | ||
} | ||
static distance(colorA, colorB) { | ||
return (Math.abs(colorB.red - colorA.red) + Math.abs(colorB.green - colorA.green) + Math.abs(colorB.blue - colorA.blue)) / (3 * 255); | ||
return (Math.abs(colorB._red - colorA._red) + Math.abs(colorB._green - colorA._green) + Math.abs(colorB._blue - colorA._blue)) / (3 * 255); | ||
} | ||
updateHSL() { | ||
const red = this.red / 255; | ||
const green = this.green / 255; | ||
const blue = this.blue / 255; | ||
const red = this._red / 255; | ||
const green = this._green / 255; | ||
const blue = this._blue / 255; | ||
const max = Math.max(red, green, blue); | ||
const min = Math.min(red, green, blue); | ||
this._lightness = (max + min) / 2; | ||
this.__lightness = (max + min) / 2; | ||
if (max === min) { | ||
this._hue = 0; | ||
this._saturation = 0; | ||
this._intensity = 0; | ||
this.__hue = 0; | ||
this.__saturation = 0; | ||
this.__intensity = 0; | ||
} else { | ||
const distance2 = max - min; | ||
this._saturation = this._lightness > 0.5 ? distance2 / (2 - max - min) : distance2 / (max + min); | ||
this._intensity = this._saturation * ((0.5 - Math.abs(0.5 - this._lightness)) * 2); | ||
this.__saturation = this.__lightness > 0.5 ? distance2 / (2 - max - min) : distance2 / (max + min); | ||
this.__intensity = this.__saturation * ((0.5 - Math.abs(0.5 - this.__lightness)) * 2); | ||
switch (max) { | ||
case red: | ||
this._hue = ((green - blue) / distance2 + (green < blue ? 6 : 0)) / 6; | ||
this.__hue = ((green - blue) / distance2 + (green < blue ? 6 : 0)) / 6; | ||
break; | ||
case green: | ||
this._hue = ((blue - red) / distance2 + 2) / 6; | ||
this.__hue = ((blue - red) / distance2 + 2) / 6; | ||
break; | ||
case blue: | ||
this._hue = ((red - green) / distance2 + 4) / 6; | ||
this.__hue = ((red - green) / distance2 + 4) / 6; | ||
break; | ||
@@ -57,55 +44,171 @@ } | ||
} | ||
get hue() { | ||
if (this._hue === -1) { | ||
get _hue() { | ||
if (this.__hue === -1) { | ||
this.updateHSL(); | ||
} | ||
return this._hue; | ||
return this.__hue; | ||
} | ||
get saturation() { | ||
if (this._saturation === -1) { | ||
get _saturation() { | ||
if (this.__saturation === -1) { | ||
this.updateHSL(); | ||
} | ||
return this._saturation; | ||
return this.__saturation; | ||
} | ||
get lightness() { | ||
if (this._lightness === -1) { | ||
get _lightness() { | ||
if (this.__lightness === -1) { | ||
this.updateHSL(); | ||
} | ||
return this._lightness; | ||
return this.__lightness; | ||
} | ||
get intensity() { | ||
if (this._intensity === -1) { | ||
get _intensity() { | ||
if (this.__intensity === -1) { | ||
this.updateHSL(); | ||
} | ||
return this._intensity; | ||
return this.__intensity; | ||
} | ||
} | ||
const distance = (a, b) => Math.abs(a - b); | ||
const hueDistance = (a, b) => Math.min(distance(a, b), distance((a + 0.5) % 1, (b + 0.5) % 1)); | ||
class AverageGroup { | ||
constructor() { | ||
this.colors = []; | ||
this._average = null; | ||
} | ||
addColor(color) { | ||
this.colors.push(color); | ||
this._average = null; | ||
} | ||
isSamePalette(color, hue, saturation, lightness) { | ||
for (let i = 0; i < this.colors.length; i++) { | ||
const currentColor = this.colors[i]; | ||
const isSame = hueDistance(currentColor._hue, color._hue) < hue && distance(currentColor._saturation, color._saturation) < saturation && distance(currentColor._lightness, color._lightness) < lightness; | ||
if (!isSame) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
get average() { | ||
if (!this._average) { | ||
const { r, g, b } = this.colors.reduce((total2, color) => { | ||
total2.r += color._red; | ||
total2.g += color._green; | ||
total2.b += color._blue; | ||
return total2; | ||
}, { r: 0, g: 0, b: 0 }); | ||
const total = this.colors.reduce((_count, color) => _count + color._count, 0); | ||
this._average = new Color( | ||
Math.round(r / this.colors.length), | ||
Math.round(g / this.colors.length), | ||
Math.round(b / this.colors.length) | ||
); | ||
this._average._count = total; | ||
} | ||
return this._average; | ||
} | ||
} | ||
class AverageManager { | ||
constructor(hue, saturation, lightness) { | ||
this._groups = []; | ||
this._hue = hue; | ||
this._saturation = saturation; | ||
this._lightness = lightness; | ||
} | ||
addColor(color) { | ||
const samePalette = this._groups.find((averageGroup) => averageGroup.isSamePalette(color, this._hue, this._saturation, this._lightness)); | ||
if (samePalette) { | ||
samePalette.addColor(color); | ||
} else { | ||
const averageGroup = new AverageGroup(); | ||
averageGroup.addColor(color); | ||
this._groups.push(averageGroup); | ||
} | ||
} | ||
getGroups() { | ||
return this._groups.map((averageGroup) => averageGroup.average); | ||
} | ||
} | ||
var sortColors = (list, _pixels, _hueDistance, _saturationDistance, _lightnessDistance) => { | ||
const averageManager = new AverageManager(_hueDistance, _saturationDistance, _lightnessDistance); | ||
list.forEach((color) => averageManager.addColor(color)); | ||
const sorted = averageManager.getGroups(); | ||
sorted.sort((a, b) => { | ||
const bPower = (b._intensity + 0.1) * (0.9 - b._count / _pixels); | ||
const aPower = (a._intensity + 0.1) * (0.9 - a._count / _pixels); | ||
return bPower - aPower; | ||
}); | ||
return sorted; | ||
}; | ||
const createFinalColor = (color, pixels) => { | ||
return { | ||
hex: `#${"0".repeat(6 - color._hex.toString(16).length)}${color._hex.toString(16)}`, | ||
red: color._red, | ||
green: color._green, | ||
blue: color._blue, | ||
area: color._count / pixels, | ||
hue: color._hue, | ||
saturation: color._saturation, | ||
lightness: color._lightness, | ||
intensity: color._intensity | ||
}; | ||
}; | ||
const testUint = (label, val, min = 0, max = Number.MAX_SAFE_INTEGER) => { | ||
if (!Number.isInteger(val) || val < min || val > max) { | ||
throw new Error(`${label} is invalid (${val})`); | ||
} | ||
return val; | ||
}; | ||
const testNumber = (label, val, min = 0, max = Number.MAX_VALUE) => { | ||
if (Number(val) !== val || val < min || val > max) { | ||
throw new Error(`${label} is invalid (${val})`); | ||
} | ||
return val; | ||
}; | ||
const testFunction = (label, val) => { | ||
if (!val || {}.toString.call(val) !== "[object Function]") { | ||
throw new Error(`${label} is invalid (${val})`); | ||
} | ||
return val; | ||
}; | ||
var cleanInputs = (options) => { | ||
var _a, _b, _c, _d, _e, _f, _g, _h; | ||
return [ | ||
testUint("pixels", (_a = options.pixels) != null ? _a : 1e4, 1), | ||
testNumber("distance", (_b = options.distance) != null ? _b : 0.12, 0, 1), | ||
testUint("splitPower", (_c = options.splitPower) != null ? _c : 10, 2, 15), | ||
testFunction("colorValidator", (_d = options.colorValidator) != null ? _d : (_red, _green, _blue, _alpha) => (_alpha != null ? _alpha : 255) > 250), | ||
testNumber("hueDistance", (_e = options.hueDistance) != null ? _e : 0.08333333333333333, 0, 1), | ||
testNumber("saturationDistance", (_f = options.saturationDistance) != null ? _f : 0.2, 0, 1), | ||
testNumber("lightnessDistance", (_g = options.lightnessDistance) != null ? _g : 0.2, 0, 1), | ||
(_h = options.crossOrigin) != null ? _h : null | ||
]; | ||
}; | ||
class BudGroup { | ||
constructor() { | ||
this.count = 1; | ||
this.children = {}; | ||
this._count = 1; | ||
this._children = {}; | ||
} | ||
addColor(hex, red, green, blue) { | ||
if (this.children[hex]) { | ||
this.children[hex].count++; | ||
addColor(_hex, _red, _green, _blue) { | ||
if (this._children[_hex]) { | ||
this._children[_hex]._count++; | ||
} else { | ||
this.children[hex] = new Color(red, green, blue, hex); | ||
this._children[_hex] = new Color(_red, _green, _blue, _hex); | ||
} | ||
return this.children[hex]; | ||
return this._children[_hex]; | ||
} | ||
getList() { | ||
return Object.keys(this.children).map((key) => this.children[key]); | ||
return Object.keys(this._children).map((key) => this._children[key]); | ||
} | ||
getMaxWeight(count) { | ||
if (this.maxWeight === void 0) { | ||
const list = this.getList().map((child) => child.count / count); | ||
getMaxWeight(_count) { | ||
if (this._maxWeight === void 0) { | ||
const list = this.getList().map((child) => child._count / _count); | ||
list.sort((a, b) => b - a); | ||
this.maxWeight = list[0] || 0; | ||
this._maxWeight = list[0] || 0; | ||
} | ||
return this.maxWeight; | ||
return this._maxWeight; | ||
} | ||
getMaxWeightColor(count) { | ||
getMaxWeightColor(_count) { | ||
const list = this.getList(); | ||
list.sort((a, b) => { | ||
return b.count / count - a.count / count; | ||
return b._count / _count - a._count / _count; | ||
}); | ||
@@ -116,3 +219,3 @@ return list[0]; | ||
const list = this.getList(); | ||
const biggest = list.reduce((a, b) => a.count >= b.count ? a : b); | ||
const biggest = list.reduce((a, b) => a._count >= b._count ? a : b); | ||
return biggest; | ||
@@ -123,60 +226,59 @@ } | ||
constructor() { | ||
this.isColor = false; | ||
this.count = 1; | ||
this.children = {}; | ||
this._count = 1; | ||
this._children = {}; | ||
} | ||
addRootGroup(key) { | ||
if (this.children[key]) { | ||
this.children[key].count++; | ||
if (this._children[key]) { | ||
this._children[key]._count++; | ||
} else { | ||
this.children[key] = new RootGroup(); | ||
this._children[key] = new RootGroup(); | ||
} | ||
return this.children[key]; | ||
return this._children[key]; | ||
} | ||
getList() { | ||
return Object.keys(this.children).map((key) => this.children[key]); | ||
return Object.keys(this._children).map((key) => this._children[key]); | ||
} | ||
addBudGroup(key) { | ||
if (this.children[key]) { | ||
this.children[key].count++; | ||
if (this._children[key]) { | ||
this._children[key]._count++; | ||
} else { | ||
this.children[key] = new BudGroup(); | ||
this._children[key] = new BudGroup(); | ||
} | ||
return this.children[key]; | ||
return this._children[key]; | ||
} | ||
getMaxWeight(count) { | ||
if (this.maxWeight === void 0) { | ||
const list = this.getList().map((child) => child.count / count); | ||
getMaxWeight(_count) { | ||
if (this._maxWeight === void 0) { | ||
const list = this.getList().map((child) => child._count / _count); | ||
list.sort((a, b) => b - a); | ||
this.maxWeight = list[0] || 0; | ||
this._maxWeight = list[0] || 0; | ||
} | ||
return this.maxWeight; | ||
return this._maxWeight; | ||
} | ||
getMaxWeightColor(count) { | ||
getMaxWeightColor(_count) { | ||
const list = this.getList(); | ||
list.sort((a, b) => { | ||
return b.count / count - a.count / count; | ||
return b._count / _count - a._count / _count; | ||
}); | ||
return list[0].getMaxWeightColor(count); | ||
return list[0].getMaxWeightColor(_count); | ||
} | ||
getMaxCountColor() { | ||
const list = this.getList(); | ||
const biggest = list.reduce((a, b) => a.getMaxCountColor().count >= b.getMaxCountColor().count ? a : b); | ||
const biggest = list.reduce((a, b) => a.getMaxCountColor()._count >= b.getMaxCountColor()._count ? a : b); | ||
return biggest.getMaxCountColor(); | ||
} | ||
getColors(distance2, count) { | ||
getColors(_distance, _count) { | ||
const list = this.getList().map((child) => { | ||
const { count: count2 } = child; | ||
const { _count: _count2 } = child; | ||
const color = child.getMaxCountColor(); | ||
color.count = count2; | ||
color._count = _count2; | ||
return color; | ||
}); | ||
list.sort((a, b) => b.count / count - a.count / count); | ||
list.sort((a, b) => b._count / _count - a._count / _count); | ||
const newList = []; | ||
list.forEach((color) => { | ||
const near = newList.find((col) => Color.distance(col, color) < distance2); | ||
const near = newList.find((col) => Color.distance(col, color) < _distance); | ||
if (!near) { | ||
newList.push(color); | ||
} else { | ||
near.count += color.count; | ||
near._count += color._count; | ||
} | ||
@@ -187,147 +289,35 @@ }); | ||
} | ||
const testUint = (label, val, min = 0, max = Number.MAX_SAFE_INTEGER) => { | ||
if (!Number.isInteger(val) || val < min || val > max) { | ||
throw new Error(`${label} is invalid (${val})`); | ||
} | ||
return val; | ||
}; | ||
const testNumber = (label, val, min = 0, max = Number.MAX_VALUE) => { | ||
if (Number(val) !== val || val < min || val > max) { | ||
throw new Error(`${label} is invalid (${val})`); | ||
} | ||
return val; | ||
}; | ||
const testFunction = (label, val) => { | ||
if (!val || {}.toString.call(val) !== "[object Function]") { | ||
throw new Error(`${label} is invalid (${val})`); | ||
} | ||
return val; | ||
}; | ||
const _Extractor = class { | ||
constructor({ | ||
pixels = _Extractor.pixelsDefault, | ||
distance: distance2 = _Extractor.distanceDefault, | ||
splitPower = _Extractor.splitPowerDefault, | ||
colorValidator = _Extractor.colorValidatorDefault | ||
} = {}) { | ||
var _a, _b, _c, _d; | ||
this.pixels = (_a = testUint("pixels", pixels, 1)) != null ? _a : _Extractor.pixelsDefault; | ||
this.splitPower = (_b = testNumber("splitPower", splitPower, 2, 15)) != null ? _b : _Extractor.splitPowerDefault; | ||
this.distance = (_c = testNumber("distance", distance2, 0, 1)) != null ? _c : _Extractor.distanceDefault; | ||
this.colorValidator = (_d = testFunction("colorValidator", colorValidator)) != null ? _d : _Extractor.colorValidatorDefault; | ||
} | ||
process({ data, width, height }) { | ||
const rootGroup = new RootGroup(); | ||
const acc = this.splitPower; | ||
const reducer = width && height ? Math.floor(Math.sqrt(width * height) / this.pixels) || 1 : 1; | ||
for (let i = 0; i < data.length; i += 4 * reducer) { | ||
const r = data[i]; | ||
const g = data[i + 1]; | ||
const b = data[i + 2]; | ||
const a = data[i + 3]; | ||
if (this.colorValidator(r, g, b, a)) { | ||
const real = r << 16 | g << 8 | b; | ||
const medium = (r >> 4 & 15) << 2 | (g >> 4 & 15) << 1 | b >> 4 & 15; | ||
const small = Math.round(r * (acc - 1) / 255) * (acc * acc) + Math.round(g * (acc - 1) / 255) * acc + Math.round(b * (acc - 1) / 255); | ||
const smallGroup = rootGroup.addRootGroup(small); | ||
const mediumGroup = smallGroup.addBudGroup(medium); | ||
mediumGroup.addColor(real, r, g, b); | ||
} | ||
var extractor = ({ data, width, height }, _pixels, _distance, _splitPower, _colorValidator) => { | ||
const rootGroup = new RootGroup(); | ||
const reducer = width && height ? Math.floor(width * height / _pixels) || 1 : 1; | ||
for (let i = 0; i < data.length; i += 4 * reducer) { | ||
const r = data[i]; | ||
const g = data[i + 1]; | ||
const b = data[i + 2]; | ||
const a = data[i + 3]; | ||
if (_colorValidator(r, g, b, a)) { | ||
const real = r << 16 | g << 8 | b; | ||
const medium = (r >> 4 & 15) << 2 | (g >> 4 & 15) << 1 | b >> 4 & 15; | ||
const small = Math.round(r * (_splitPower - 1) / 255) * (_splitPower * _splitPower) + Math.round(g * (_splitPower - 1) / 255) * _splitPower + Math.round(b * (_splitPower - 1) / 255); | ||
const smallGroup = rootGroup.addRootGroup(small); | ||
const mediumGroup = smallGroup.addBudGroup(medium); | ||
mediumGroup.addColor(real, r, g, b); | ||
} | ||
return rootGroup.getColors(this.distance, this.pixels); | ||
} | ||
return rootGroup.getColors(_distance, _pixels); | ||
}; | ||
let Extractor = _Extractor; | ||
Extractor.pixelsDefault = 1e4; | ||
Extractor.distanceDefault = 0.12; | ||
Extractor.splitPowerDefault = 10; | ||
Extractor.colorValidatorDefault = (_red, _green, _blue, alpha) => (alpha != null ? alpha : 255) > 250; | ||
const distance = (a, b) => Math.abs(a - b); | ||
const hueDistance = (a, b) => Math.min(distance(a, b), distance((a + 0.5) % 1, (b + 0.5) % 1)); | ||
class AverageGroup { | ||
constructor() { | ||
this.colors = []; | ||
this._average = null; | ||
} | ||
addColor(color) { | ||
this.colors.push(color); | ||
this._average = null; | ||
} | ||
isSamePalette(color, hue, saturation, lightness) { | ||
for (let i = 0; i < this.colors.length; i++) { | ||
const currentColor = this.colors[i]; | ||
const isSame = hueDistance(currentColor.hue, color.hue) < hue && distance(currentColor.saturation, color.saturation) < saturation && distance(currentColor.lightness, color.lightness) < lightness; | ||
if (!isSame) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
get average() { | ||
if (!this._average) { | ||
const { red, green, blue } = this.colors.reduce((total2, color) => { | ||
total2.red += color.red; | ||
total2.green += color.green; | ||
total2.blue += color.blue; | ||
return total2; | ||
}, { red: 0, green: 0, blue: 0 }); | ||
const total = this.colors.reduce((count, color) => count + color.count, 0); | ||
this._average = new Color( | ||
Math.round(red / this.colors.length), | ||
Math.round(green / this.colors.length), | ||
Math.round(blue / this.colors.length) | ||
); | ||
this._average.count = total; | ||
} | ||
return this._average; | ||
} | ||
} | ||
const _AverageManager = class { | ||
constructor({ | ||
hue = _AverageManager.hueDefault, | ||
saturation = _AverageManager.saturationDefault, | ||
lightness = _AverageManager.lightnessDefault | ||
} = {}) { | ||
this._groups = []; | ||
this.hue = hue; | ||
this.saturation = saturation; | ||
this.lightness = lightness; | ||
} | ||
addColor(color) { | ||
const samePalette = this._groups.find((averageGroup) => averageGroup.isSamePalette(color, this.hue, this.saturation, this.lightness)); | ||
if (samePalette) { | ||
samePalette.addColor(color); | ||
} else { | ||
const averageGroup = new AverageGroup(); | ||
averageGroup.addColor(color); | ||
this._groups.push(averageGroup); | ||
} | ||
} | ||
getGroups() { | ||
return this._groups.map((averageGroup) => averageGroup.average); | ||
} | ||
const sortFinalColors = (_colors, _pixels, _hueDistance, _saturationDistance, _lightnessDistance) => { | ||
const list = sortColors(_colors, _pixels, _hueDistance, _saturationDistance, _lightnessDistance); | ||
return list.map((color) => createFinalColor(color, _pixels)); | ||
}; | ||
let AverageManager = _AverageManager; | ||
AverageManager.hueDefault = 1 / 12; | ||
AverageManager.saturationDefault = 1 / 5; | ||
AverageManager.lightnessDefault = 1 / 5; | ||
var sortColors = (list, pixels, { | ||
saturationDistance, | ||
lightnessDistance, | ||
hueDistance: hueDistance2 | ||
} = {}) => { | ||
const averageManager = new AverageManager({ hue: hueDistance2, saturation: saturationDistance, lightness: lightnessDistance }); | ||
list.forEach((color) => averageManager.addColor(color)); | ||
const sorted = averageManager.getGroups(); | ||
sorted.sort((a, b) => { | ||
const bPower = (b.intensity + 0.1) * (0.9 - b.count / pixels); | ||
const aPower = (a.intensity + 0.1) * (0.9 - a.count / pixels); | ||
return bPower - aPower; | ||
}); | ||
return sorted; | ||
const extractColorsFromImageData = (imageData, options = {}) => { | ||
const [_pixels, _distance, _splitPower, _colorValidator, _hueDistance, _saturationDistance, _lightnessDistance] = cleanInputs(options); | ||
const colors = extractor(imageData, _pixels, _distance, _splitPower, _colorValidator); | ||
const px = imageData.width && imageData.height ? Math.min(imageData.width * imageData.height, _pixels) : _pixels; | ||
return sortFinalColors(colors, px, _hueDistance, _saturationDistance, _lightnessDistance); | ||
}; | ||
const getImageData = (image, pixels) => { | ||
const currentPixels = image.width * image.height; | ||
const width = currentPixels < pixels ? image.width : Math.round(image.width * Math.sqrt(pixels / currentPixels)); | ||
const height = currentPixels < pixels ? image.height : Math.round(image.height * Math.sqrt(pixels / currentPixels)); | ||
const getImageData = (_image, _pixels) => { | ||
const currentPixels = _image.width * _image.height; | ||
const width = currentPixels < _pixels ? _image.width : Math.round(_image.width * Math.sqrt(_pixels / currentPixels)); | ||
const height = currentPixels < _pixels ? _image.height : Math.round(_image.height * Math.sqrt(_pixels / currentPixels)); | ||
const canvas = document.createElement("canvas"); | ||
@@ -337,29 +327,20 @@ canvas.width = width; | ||
const context = canvas.getContext("2d"); | ||
context.drawImage(image, 0, 0, image.width, image.height, 0, 0, width, height); | ||
context.drawImage(_image, 0, 0, _image.width, _image.height, 0, 0, width, height); | ||
return context.getImageData(0, 0, width, height); | ||
}; | ||
const sortFinalColors = (colors, pixels, options) => { | ||
const list = sortColors(colors, pixels, options); | ||
return list.map((color) => createFinalColor(color, pixels)); | ||
}; | ||
const extractColorsFromImageData = (imageData, options) => { | ||
const extractor = new Extractor(options); | ||
const colors = extractor.process(imageData); | ||
return sortFinalColors(colors, extractor.pixels, options); | ||
}; | ||
const extractColorsFromImage = (image, options) => { | ||
image.crossOrigin = (options == null ? void 0 : options.crossOrigin) || null; | ||
const extractColorsFromImage = (image, options = {}) => { | ||
const [_pixels, _distance, _splitPower, _colorValidator, _hueDistance, _saturationDistance, _lightnessDistance, _crossOrigin] = cleanInputs(options); | ||
image.crossOrigin = _crossOrigin; | ||
return new Promise((resolve) => { | ||
const extract = (image2, options2) => { | ||
const extractor = new Extractor(options2); | ||
const imageData = getImageData(image2, extractor.pixels); | ||
const colors = extractor.process(imageData); | ||
resolve(sortFinalColors(colors, extractor.pixels, options2)); | ||
const extract = (image2) => { | ||
const imageData = getImageData(image2, _pixels); | ||
const _colors = extractor(imageData, _pixels, _distance, _splitPower, _colorValidator); | ||
resolve(sortFinalColors(_colors, _pixels, _hueDistance, _saturationDistance, _lightnessDistance)); | ||
}; | ||
if (image.complete) { | ||
extract(image, options); | ||
extract(image); | ||
} else { | ||
const imageLoaded = () => { | ||
image.removeEventListener("load", imageLoaded); | ||
extract(image, options); | ||
extract(image); | ||
}; | ||
@@ -370,3 +351,3 @@ image.addEventListener("load", imageLoaded); | ||
}; | ||
const extractColorsFromSrc = (src, options) => { | ||
const extractColorsFromSrc = (src, options = {}) => { | ||
const image = new Image(); | ||
@@ -373,0 +354,0 @@ image.src = src; |
@@ -0,12 +1,5 @@ | ||
import { extractColorsFromImageData } from "./extractColors"; | ||
import { FinalColor } from "./types/Color"; | ||
import type { BrowserOptions, NodeOptions } from "./types/Options"; | ||
import type { BrowserOptions } from "./types/Options"; | ||
/** | ||
* Extract colors from an ImageData object. | ||
*/ | ||
declare const extractColorsFromImageData: (imageData: ImageData | { | ||
data: Uint8ClampedArray | number[]; | ||
width?: number; | ||
height?: number; | ||
}, options?: NodeOptions) => FinalColor[]; | ||
/** | ||
* Extract colors from an HTMLImageElement. | ||
@@ -13,0 +6,0 @@ */ |
@@ -1,2 +0,2 @@ | ||
(function(h,g){typeof exports=="object"&&typeof module!="undefined"?g(exports):typeof define=="function"&&define.amd?define(["exports"],g):(h=typeof globalThis!="undefined"?globalThis:h||self,g(h.ExtractColors={}))})(this,function(h){"use strict";const g=(e,t)=>({hex:`#${"0".repeat(6-e.hex.toString(16).length)}${e.hex.toString(16)}`,red:e.red,green:e.green,blue:e.blue,area:e.count/t,hue:e.hue,saturation:e.saturation,lightness:e.lightness,intensity:e.intensity});class C{constructor(t,s,r,n=t<<16|s<<8|r){this.count=1,this._saturation=-1,this._hue=-1,this._lightness=-1,this._intensity=-1,this.red=t,this.green=s,this.blue=r,this.hex=n}static distance(t,s){return(Math.abs(s.red-t.red)+Math.abs(s.green-t.green)+Math.abs(s.blue-t.blue))/(3*255)}updateHSL(){const t=this.red/255,s=this.green/255,r=this.blue/255,n=Math.max(t,s,r),o=Math.min(t,s,r);if(this._lightness=(n+o)/2,n===o)this._hue=0,this._saturation=0,this._intensity=0;else{const i=n-o;switch(this._saturation=this._lightness>.5?i/(2-n-o):i/(n+o),this._intensity=this._saturation*((.5-Math.abs(.5-this._lightness))*2),n){case t:this._hue=((s-r)/i+(s<r?6:0))/6;break;case s:this._hue=((r-t)/i+2)/6;break;case r:this._hue=((t-s)/i+4)/6;break}}}get hue(){return this._hue===-1&&this.updateHSL(),this._hue}get saturation(){return this._saturation===-1&&this.updateHSL(),this._saturation}get lightness(){return this._lightness===-1&&this.updateHSL(),this._lightness}get intensity(){return this._intensity===-1&&this.updateHSL(),this._intensity}}class E{constructor(){this.count=1,this.children={}}addColor(t,s,r,n){return this.children[t]?this.children[t].count++:this.children[t]=new C(s,r,n,t),this.children[t]}getList(){return Object.keys(this.children).map(t=>this.children[t])}getMaxWeight(t){if(this.maxWeight===void 0){const s=this.getList().map(r=>r.count/t);s.sort((r,n)=>n-r),this.maxWeight=s[0]||0}return this.maxWeight}getMaxWeightColor(t){const s=this.getList();return s.sort((r,n)=>n.count/t-r.count/t),s[0]}getMaxCountColor(){return this.getList().reduce((r,n)=>r.count>=n.count?r:n)}}class x{constructor(){this.isColor=!1,this.count=1,this.children={}}addRootGroup(t){return this.children[t]?this.children[t].count++:this.children[t]=new x,this.children[t]}getList(){return Object.keys(this.children).map(t=>this.children[t])}addBudGroup(t){return this.children[t]?this.children[t].count++:this.children[t]=new E,this.children[t]}getMaxWeight(t){if(this.maxWeight===void 0){const s=this.getList().map(r=>r.count/t);s.sort((r,n)=>n-r),this.maxWeight=s[0]||0}return this.maxWeight}getMaxWeightColor(t){const s=this.getList();return s.sort((r,n)=>n.count/t-r.count/t),s[0].getMaxWeightColor(t)}getMaxCountColor(){return this.getList().reduce((r,n)=>r.getMaxCountColor().count>=n.getMaxCountColor().count?r:n).getMaxCountColor()}getColors(t,s){const r=this.getList().map(o=>{const{count:i}=o,a=o.getMaxCountColor();return a.count=i,a});r.sort((o,i)=>i.count/s-o.count/s);const n=[];return r.forEach(o=>{const i=n.find(a=>C.distance(a,o)<t);i?i.count+=o.count:n.push(o)}),n}}const G=(e,t,s=0,r=Number.MAX_SAFE_INTEGER)=>{if(!Number.isInteger(t)||t<s||t>r)throw new Error(`${e} is invalid (${t})`);return t},b=(e,t,s=0,r=Number.MAX_VALUE)=>{if(Number(t)!==t||t<s||t>r)throw new Error(`${e} is invalid (${t})`);return t},P=(e,t)=>{if(!t||{}.toString.call(t)!=="[object Function]")throw new Error(`${e} is invalid (${t})`);return t},c=class{constructor({pixels:e=c.pixelsDefault,distance:t=c.distanceDefault,splitPower:s=c.splitPowerDefault,colorValidator:r=c.colorValidatorDefault}={}){var n,o,i,a;this.pixels=(n=G("pixels",e,1))!=null?n:c.pixelsDefault,this.splitPower=(o=b("splitPower",s,2,15))!=null?o:c.splitPowerDefault,this.distance=(i=b("distance",t,0,1))!=null?i:c.distanceDefault,this.colorValidator=(a=P("colorValidator",r))!=null?a:c.colorValidatorDefault}process({data:e,width:t,height:s}){const r=new x,n=this.splitPower,o=t&&s&&Math.floor(Math.sqrt(t*s)/this.pixels)||1;for(let i=0;i<e.length;i+=4*o){const a=e[i],u=e[i+1],l=e[i+2],w=e[i+3];if(this.colorValidator(a,u,l,w)){const W=a<<16|u<<8|l,$=(a>>4&15)<<2|(u>>4&15)<<1|l>>4&15,V=Math.round(a*(n-1)/255)*(n*n)+Math.round(u*(n-1)/255)*n+Math.round(l*(n-1)/255);r.addRootGroup(V).addBudGroup($).addColor(W,a,u,l)}}return r.getColors(this.distance,this.pixels)}};let d=c;d.pixelsDefault=1e4,d.distanceDefault=.12,d.splitPowerDefault=10,d.colorValidatorDefault=(e,t,s,r)=>(r!=null?r:255)>250;const f=(e,t)=>Math.abs(e-t),v=(e,t)=>Math.min(f(e,t),f((e+.5)%1,(t+.5)%1));class I{constructor(){this.colors=[],this._average=null}addColor(t){this.colors.push(t),this._average=null}isSamePalette(t,s,r,n){for(let o=0;o<this.colors.length;o++){const i=this.colors[o];if(!(v(i.hue,t.hue)<s&&f(i.saturation,t.saturation)<r&&f(i.lightness,t.lightness)<n))return!1}return!0}get average(){if(!this._average){const{red:t,green:s,blue:r}=this.colors.reduce((o,i)=>(o.red+=i.red,o.green+=i.green,o.blue+=i.blue,o),{red:0,green:0,blue:0}),n=this.colors.reduce((o,i)=>o+i.count,0);this._average=new C(Math.round(t/this.colors.length),Math.round(s/this.colors.length),Math.round(r/this.colors.length)),this._average.count=n}return this._average}}const m=class{constructor({hue:e=m.hueDefault,saturation:t=m.saturationDefault,lightness:s=m.lightnessDefault}={}){this._groups=[],this.hue=e,this.saturation=t,this.lightness=s}addColor(e){const t=this._groups.find(s=>s.isSamePalette(e,this.hue,this.saturation,this.lightness));if(t)t.addColor(e);else{const s=new I;s.addColor(e),this._groups.push(s)}}getGroups(){return this._groups.map(e=>e.average)}};let _=m;_.hueDefault=1/12,_.saturationDefault=1/5,_.lightnessDefault=1/5;var y=(e,t,{saturationDistance:s,lightnessDistance:r,hueDistance:n}={})=>{const o=new _({hue:n,saturation:s,lightness:r});e.forEach(a=>o.addColor(a));const i=o.getGroups();return i.sort((a,u)=>{const l=(u.intensity+.1)*(.9-u.count/t),w=(a.intensity+.1)*(.9-a.count/t);return l-w}),i};const F=(e,t)=>{const s=e.width*e.height,r=s<t?e.width:Math.round(e.width*Math.sqrt(t/s)),n=s<t?e.height:Math.round(e.height*Math.sqrt(t/s)),o=document.createElement("canvas");o.width=r,o.height=n;const i=o.getContext("2d");return i.drawImage(e,0,0,e.width,e.height,0,0,r,n),i.getImageData(0,0,r,n)},p=(e,t,s)=>y(e,t,s).map(n=>g(n,t)),D=(e,t)=>{const s=new d(t),r=s.process(e);return p(r,s.pixels,t)},M=(e,t)=>(e.crossOrigin=(t==null?void 0:t.crossOrigin)||null,new Promise(s=>{const r=(n,o)=>{const i=new d(o),a=F(n,i.pixels),u=i.process(a);s(p(u,i.pixels,o))};if(e.complete)r(e,t);else{const n=()=>{e.removeEventListener("load",n),r(e,t)};e.addEventListener("load",n)}})),L=(e,t)=>{const s=new Image;return s.src=e,M(s,t)},S=(e,t)=>{if(e instanceof Image)return M(e,t);if(e instanceof ImageData||e instanceof Object&&e.data)return new Promise(s=>{s(D(e,t))});if(typeof e=="string")return L(e,t);throw new Error("Can not analyse picture")};h.default=S,h.extractColors=S,h.extractColorsFromImage=M,h.extractColorsFromImageData=D,h.extractColorsFromSrc=L,Object.defineProperties(h,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}); | ||
!function(t,s){"object"==typeof exports&&"undefined"!=typeof module?s(exports):"function"==typeof define&&define.amd?define(["exports"],s):s((t="undefined"!=typeof globalThis?globalThis:t||self).t={})}(this,(function(t){"use strict";class s{constructor(t,s,i,e=t<<16|s<<8|i){this.i=1,this.h=-1,this.o=-1,this.u=-1,this.l=-1,this.g=t,this.M=s,this.p=i,this.m=e}static distance(t,s){return(Math.abs(s.g-t.g)+Math.abs(s.M-t.M)+Math.abs(s.p-t.p))/765}v(){const t=this.g/255,s=this.M/255,i=this.p/255,e=Math.max(t,s,i),n=Math.min(t,s,i);if(this.u=(e+n)/2,e===n)this.o=0,this.h=0,this.l=0;else{const r=e-n;switch(this.h=this.u>.5?r/(2-e-n):r/(e+n),this.l=this.h*(2*(.5-Math.abs(.5-this.u))),e){case t:this.o=((s-i)/r+(s<i?6:0))/6;break;case s:this.o=((i-t)/r+2)/6;break;case i:this.o=((t-s)/r+4)/6}}}get C(){return-1===this.o&&this.v(),this.o}get $(){return-1===this.h&&this.v(),this.h}get _(){return-1===this.u&&this.v(),this.u}get j(){return-1===this.l&&this.v(),this.l}}const i=(t,s)=>Math.abs(t-s);class e{constructor(){this.D=[],this.N=null}O(t){this.D.push(t),this.N=null}P(t,s,e,n){for(let o=0;o<this.D.length;o++){const a=this.D[o];if(!(r=a.C,h=t.C,Math.min(i(r,h),i((r+.5)%1,(h+.5)%1))<s&&i(a.$,t.$)<e&&i(a._,t._)<n))return!1}var r,h;return!0}get W(){if(!this.N){const{r:t,G:i,b:e}=this.D.reduce(((t,s)=>(t.r+=s.g,t.G+=s.M,t.b+=s.p,t)),{r:0,G:0,b:0}),n=this.D.reduce(((t,s)=>t+s.i),0);this.N=new s(Math.round(t/this.D.length),Math.round(i/this.D.length),Math.round(e/this.D.length)),this.N.i=n}return this.N}}class n{constructor(t,s,i){this.I=[],this.C=t,this.$=s,this._=i}O(t){const s=this.I.find((s=>s.P(t,this.C,this.$,this._)));if(s)s.O(t);else{const s=new e;s.O(t),this.I.push(s)}}L(){return this.I.map((t=>t.W))}}const r=(t,s,i=0,e=Number.MAX_SAFE_INTEGER)=>{if(!Number.isInteger(s)||s<i||s>e)throw new Error(`${t} is invalid (${s})`);return s},h=(t,s,i=0,e=Number.MAX_VALUE)=>{if(Number(s)!==s||s<i||s>e)throw new Error(`${t} is invalid (${s})`);return s},o=(t,s)=>{if(!s||"[object Function]"!=={}.toString.call(s))throw new Error(`${t} is invalid (${s})`);return s};var a=t=>{var s,i,e,n,a,u,c,l;return[r("pixels",null!=(s=t.pixels)?s:1e4,1),h("distance",null!=(i=t.distance)?i:.12,0,1),r("splitPower",null!=(e=t.splitPower)?e:10,2,15),o("colorValidator",null!=(n=t.colorValidator)?n:(t,s,i,e)=>(null!=e?e:255)>250),h("hueDistance",null!=(a=t.hueDistance)?a:.08333333333333333,0,1),h("saturationDistance",null!=(u=t.saturationDistance)?u:.2,0,1),h("lightnessDistance",null!=(c=t.lightnessDistance)?c:.2,0,1),null!=(l=t.crossOrigin)?l:null]};class u{constructor(){this.i=1,this.S={}}O(t,i,e,n){return this.S[t]?this.S[t].i++:this.S[t]=new s(i,e,n,t),this.S[t]}k(){return Object.keys(this.S).map((t=>this.S[t]))}T(t){if(void 0===this.B){const s=this.k().map((s=>s.i/t));s.sort(((t,s)=>s-t)),this.B=s[0]||0}return this.B}F(t){const s=this.k();return s.sort(((s,i)=>i.i/t-s.i/t)),s[0]}H(){return this.k().reduce(((t,s)=>t.i>=s.i?t:s))}}class c{constructor(){this.i=1,this.S={}}R(t){return this.S[t]?this.S[t].i++:this.S[t]=new c,this.S[t]}k(){return Object.keys(this.S).map((t=>this.S[t]))}V(t){return this.S[t]?this.S[t].i++:this.S[t]=new u,this.S[t]}T(t){if(void 0===this.B){const s=this.k().map((s=>s.i/t));s.sort(((t,s)=>s-t)),this.B=s[0]||0}return this.B}F(t){const s=this.k();return s.sort(((s,i)=>i.i/t-s.i/t)),s[0].F(t)}H(){return this.k().reduce(((t,s)=>t.H().i>=s.H().i?t:s)).H()}q(t,i){const e=this.k().map((t=>{const{i:s}=t,i=t.H();return i.i=s,i}));e.sort(((t,s)=>s.i/i-t.i/i));const n=[];return e.forEach((i=>{const e=n.find((e=>s.distance(e,i)<t));e?e.i+=i.i:n.push(i)})),n}}var l=({data:t,width:s,height:i},e,n,r,h)=>{const o=new c,a=s&&i&&Math.floor(s*i/e)||1;for(let u=0;u<t.length;u+=4*a){const s=t[u],i=t[u+1],e=t[u+2];if(h(s,i,e,t[u+3])){const t=s<<16|i<<8|e,n=(s>>4&15)<<2|(i>>4&15)<<1|e>>4&15,h=Math.round(s*(r-1)/255)*(r*r)+Math.round(i*(r-1)/255)*r+Math.round(e*(r-1)/255);o.R(h).V(n).O(t,s,i,e)}}return o.q(n,e)};const d=(t,s,i,e,r)=>{const h=((t,s,i,e,r)=>{const h=new n(i,e,r);t.forEach((t=>h.O(t)));const o=h.L();return o.sort(((t,i)=>(i.j+.1)*(.9-i.i/s)-(t.j+.1)*(.9-t.i/s))),o})(t,s,i,e,r);return h.map((t=>((t,s)=>({hex:`#${"0".repeat(6-t.m.toString(16).length)}${t.m.toString(16)}`,red:t.g,green:t.M,blue:t.p,area:t.i/s,hue:t.C,saturation:t.$,lightness:t._,intensity:t.j}))(t,s)))},f=(t,s={})=>{const[i,e,n,r,h,o,u]=a(s),c=l(t,i,e,n,r),f=t.width&&t.height?Math.min(t.width*t.height,i):i;return d(c,f,h,o,u)},g=(t,s={})=>{const[i,e,n,r,h,o,u,c]=a(s);return t.crossOrigin=c,new Promise((s=>{const a=t=>{const a=((t,s)=>{const i=t.width*t.height,e=i<s?t.width:Math.round(t.width*Math.sqrt(s/i)),n=i<s?t.height:Math.round(t.height*Math.sqrt(s/i)),r=document.createElement("canvas");r.width=e,r.height=n;const h=r.getContext("2d");return h.drawImage(t,0,0,t.width,t.height,0,0,e,n),h.getImageData(0,0,e,n)})(t,i),c=l(a,i,e,n,r);s(d(c,i,h,o,u))};if(t.complete)a(t);else{const s=()=>{t.removeEventListener("load",s),a(t)};t.addEventListener("load",s)}}))},M=(t,s={})=>{const i=new Image;return i.src=t,g(i,s)},w=(t,s)=>{if(t instanceof Image)return g(t,s);if(t instanceof ImageData||t instanceof Object&&t.data)return new Promise((i=>{i(f(t,s))}));if("string"==typeof t)return M(t,s);throw new Error("Can not analyse picture")};t.default=w,t.extractColors=w,t.extractColorsFromImage=g,t.extractColorsFromImageData=f,t.extractColorsFromSrc=M,Object.defineProperties(t,{A:{value:!0},[Symbol.toStringTag]:{value:"Module"}})})); | ||
//# sourceMappingURL=extract-colors.browser.umd.js.map |
@@ -0,12 +1,5 @@ | ||
import { extractColorsFromImageData } from './extractColors'; | ||
import { FinalColor } from './types/Color'; | ||
import type { NodeOptions } from "./types/Options"; | ||
/** | ||
* Extract colors from an ImageData object. | ||
*/ | ||
declare const extractColorsFromImageData: ({ data, width, height }: { | ||
data: Uint8ClampedArray | number[]; | ||
width?: number | undefined; | ||
height?: number | undefined; | ||
}, options?: NodeOptions) => FinalColor[]; | ||
/** | ||
* Extract colors from an imageData. | ||
@@ -13,0 +6,0 @@ */ |
@@ -1,2 +0,2 @@ | ||
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const S=(r,t)=>({hex:`#${"0".repeat(6-r.hex.toString(16).length)}${r.hex.toString(16)}`,red:r.red,green:r.green,blue:r.blue,area:r.count/t,hue:r.hue,saturation:r.saturation,lightness:r.lightness,intensity:r.intensity});class _{constructor(t,e,s,i=t<<16|e<<8|s){this.count=1,this._saturation=-1,this._hue=-1,this._lightness=-1,this._intensity=-1,this.red=t,this.green=e,this.blue=s,this.hex=i}static distance(t,e){return(Math.abs(e.red-t.red)+Math.abs(e.green-t.green)+Math.abs(e.blue-t.blue))/(3*255)}updateHSL(){const t=this.red/255,e=this.green/255,s=this.blue/255,i=Math.max(t,e,s),n=Math.min(t,e,s);if(this._lightness=(i+n)/2,i===n)this._hue=0,this._saturation=0,this._intensity=0;else{const o=i-n;switch(this._saturation=this._lightness>.5?o/(2-i-n):o/(i+n),this._intensity=this._saturation*((.5-Math.abs(.5-this._lightness))*2),i){case t:this._hue=((e-s)/o+(e<s?6:0))/6;break;case e:this._hue=((s-t)/o+2)/6;break;case s:this._hue=((t-e)/o+4)/6;break}}}get hue(){return this._hue===-1&&this.updateHSL(),this._hue}get saturation(){return this._saturation===-1&&this.updateHSL(),this._saturation}get lightness(){return this._lightness===-1&&this.updateHSL(),this._lightness}get intensity(){return this._intensity===-1&&this.updateHSL(),this._intensity}}class E{constructor(){this.count=1,this.children={}}addColor(t,e,s,i){return this.children[t]?this.children[t].count++:this.children[t]=new _(e,s,i,t),this.children[t]}getList(){return Object.keys(this.children).map(t=>this.children[t])}getMaxWeight(t){if(this.maxWeight===void 0){const e=this.getList().map(s=>s.count/t);e.sort((s,i)=>i-s),this.maxWeight=e[0]||0}return this.maxWeight}getMaxWeightColor(t){const e=this.getList();return e.sort((s,i)=>i.count/t-s.count/t),e[0]}getMaxCountColor(){return this.getList().reduce((s,i)=>s.count>=i.count?s:i)}}class p{constructor(){this.isColor=!1,this.count=1,this.children={}}addRootGroup(t){return this.children[t]?this.children[t].count++:this.children[t]=new p,this.children[t]}getList(){return Object.keys(this.children).map(t=>this.children[t])}addBudGroup(t){return this.children[t]?this.children[t].count++:this.children[t]=new E,this.children[t]}getMaxWeight(t){if(this.maxWeight===void 0){const e=this.getList().map(s=>s.count/t);e.sort((s,i)=>i-s),this.maxWeight=e[0]||0}return this.maxWeight}getMaxWeightColor(t){const e=this.getList();return e.sort((s,i)=>i.count/t-s.count/t),e[0].getMaxWeightColor(t)}getMaxCountColor(){return this.getList().reduce((s,i)=>s.getMaxCountColor().count>=i.getMaxCountColor().count?s:i).getMaxCountColor()}getColors(t,e){const s=this.getList().map(n=>{const{count:o}=n,a=n.getMaxCountColor();return a.count=o,a});s.sort((n,o)=>o.count/e-n.count/e);const i=[];return s.forEach(n=>{const o=i.find(a=>_.distance(a,n)<t);o?o.count+=n.count:i.push(n)}),i}}const G=(r,t,e=0,s=Number.MAX_SAFE_INTEGER)=>{if(!Number.isInteger(t)||t<e||t>s)throw new Error(`${r} is invalid (${t})`);return t},C=(r,t,e=0,s=Number.MAX_VALUE)=>{if(Number(t)!==t||t<e||t>s)throw new Error(`${r} is invalid (${t})`);return t},L=(r,t)=>{if(!t||{}.toString.call(t)!=="[object Function]")throw new Error(`${r} is invalid (${t})`);return t},u=class{constructor({pixels:r=u.pixelsDefault,distance:t=u.distanceDefault,splitPower:e=u.splitPowerDefault,colorValidator:s=u.colorValidatorDefault}={}){var i,n,o,a;this.pixels=(i=G("pixels",r,1))!=null?i:u.pixelsDefault,this.splitPower=(n=C("splitPower",e,2,15))!=null?n:u.splitPowerDefault,this.distance=(o=C("distance",t,0,1))!=null?o:u.distanceDefault,this.colorValidator=(a=L("colorValidator",s))!=null?a:u.colorValidatorDefault}process({data:r,width:t,height:e}){const s=new p,i=this.splitPower,n=t&&e&&Math.floor(Math.sqrt(t*e)/this.pixels)||1;for(let o=0;o<r.length;o+=4*n){const a=r[o],h=r[o+1],c=r[o+2],f=r[o+3];if(this.colorValidator(a,h,c,f)){const b=a<<16|h<<8|c,w=(a>>4&15)<<2|(h>>4&15)<<1|c>>4&15,D=Math.round(a*(i-1)/255)*(i*i)+Math.round(h*(i-1)/255)*i+Math.round(c*(i-1)/255);s.addRootGroup(D).addBudGroup(w).addColor(b,a,h,c)}}return s.getColors(this.distance,this.pixels)}};let l=u;l.pixelsDefault=1e4;l.distanceDefault=.12;l.splitPowerDefault=10;l.colorValidatorDefault=(r,t,e,s)=>(s!=null?s:255)>250;const d=(r,t)=>Math.abs(r-t),F=(r,t)=>Math.min(d(r,t),d((r+.5)%1,(t+.5)%1));class P{constructor(){this.colors=[],this._average=null}addColor(t){this.colors.push(t),this._average=null}isSamePalette(t,e,s,i){for(let n=0;n<this.colors.length;n++){const o=this.colors[n];if(!(F(o.hue,t.hue)<e&&d(o.saturation,t.saturation)<s&&d(o.lightness,t.lightness)<i))return!1}return!0}get average(){if(!this._average){const{red:t,green:e,blue:s}=this.colors.reduce((n,o)=>(n.red+=o.red,n.green+=o.green,n.blue+=o.blue,n),{red:0,green:0,blue:0}),i=this.colors.reduce((n,o)=>n+o.count,0);this._average=new _(Math.round(t/this.colors.length),Math.round(e/this.colors.length),Math.round(s/this.colors.length)),this._average.count=i}return this._average}}const g=class{constructor({hue:r=g.hueDefault,saturation:t=g.saturationDefault,lightness:e=g.lightnessDefault}={}){this._groups=[],this.hue=r,this.saturation=t,this.lightness=e}addColor(r){const t=this._groups.find(e=>e.isSamePalette(r,this.hue,this.saturation,this.lightness));if(t)t.addColor(r);else{const e=new P;e.addColor(r),this._groups.push(e)}}getGroups(){return this._groups.map(r=>r.average)}};let m=g;m.hueDefault=1/12;m.saturationDefault=1/5;m.lightnessDefault=1/5;var N=(r,t,{saturationDistance:e,lightnessDistance:s,hueDistance:i}={})=>{const n=new m({hue:i,saturation:e,lightness:s});r.forEach(a=>n.addColor(a));const o=n.getGroups();return o.sort((a,h)=>{const c=(h.intensity+.1)*(.9-h.count/t),f=(a.intensity+.1)*(.9-a.count/t);return c-f}),o};const W=(r,t,e)=>N(r,t,e).map(i=>S(i,t)),x=({data:r,width:t=Number.MAX_SAFE_INTEGER,height:e=Number.MAX_SAFE_INTEGER},s)=>{const i=new l(s),n=i.process({data:r,width:t,height:e});return W(n,i.pixels,s)},M=(r,t)=>{if(r.data)return new Promise(e=>{e(x(r,t))});throw new Error("Send imageData to extractColors")},v=()=>{throw new Error("Can not use extractColorsFromImage for Node.js")},A=()=>{throw new Error("Can not use extractColorsFromSrc for Node.js")};exports.default=M;exports.extractColors=M;exports.extractColorsFromImage=v;exports.extractColorsFromImageData=x;exports.extractColorsFromSrc=A; | ||
"use strict";Object.defineProperties(exports,{t:{value:!0},[Symbol.toStringTag]:{value:"Module"}});class t{constructor(t,s,i,r=t<<16|s<<8|i){this.i=1,this.h=-1,this.o=-1,this.u=-1,this.l=-1,this.g=t,this.M=s,this.C=i,this.p=r}static distance(t,s){return(Math.abs(s.g-t.g)+Math.abs(s.M-t.M)+Math.abs(s.C-t.C))/765}m(){const t=this.g/255,s=this.M/255,i=this.C/255,r=Math.max(t,s,i),e=Math.min(t,s,i);if(this.u=(r+e)/2,r===e)this.o=0,this.h=0,this.l=0;else{const h=r-e;switch(this.h=this.u>.5?h/(2-r-e):h/(r+e),this.l=this.h*(2*(.5-Math.abs(.5-this.u))),r){case t:this.o=((s-i)/h+(s<i?6:0))/6;break;case s:this.o=((i-t)/h+2)/6;break;case i:this.o=((t-s)/h+4)/6}}}get v(){return-1===this.o&&this.m(),this.o}get $(){return-1===this.h&&this.m(),this.h}get _(){return-1===this.u&&this.m(),this.u}get j(){return-1===this.l&&this.m(),this.l}}const s=(t,s)=>Math.abs(t-s);class i{constructor(){this.N=[],this.S=null}D(t){this.N.push(t),this.S=null}W(t,i,r,e){for(let o=0;o<this.N.length;o++){const a=this.N[o];if(!(h=a.v,n=t.v,Math.min(s(h,n),s((h+.5)%1,(n+.5)%1))<i&&s(a.$,t.$)<r&&s(a._,t._)<e))return!1}var h,n;return!0}get F(){if(!this.S){const{r:s,G:i,b:r}=this.N.reduce(((t,s)=>(t.r+=s.g,t.G+=s.M,t.b+=s.C,t)),{r:0,G:0,b:0}),e=this.N.reduce(((t,s)=>t+s.i),0);this.S=new t(Math.round(s/this.N.length),Math.round(i/this.N.length),Math.round(r/this.N.length)),this.S.i=e}return this.S}}class r{constructor(t,s,i){this.L=[],this.v=t,this.$=s,this._=i}D(t){const s=this.L.find((s=>s.W(t,this.v,this.$,this._)));if(s)s.D(t);else{const s=new i;s.D(t),this.L.push(s)}}O(){return this.L.map((t=>t.F))}}const e=(t,s,i=0,r=Number.MAX_SAFE_INTEGER)=>{if(!Number.isInteger(s)||s<i||s>r)throw new Error(`${t} is invalid (${s})`);return s},h=(t,s,i=0,r=Number.MAX_VALUE)=>{if(Number(s)!==s||s<i||s>r)throw new Error(`${t} is invalid (${s})`);return s},n=(t,s)=>{if(!s||"[object Function]"!=={}.toString.call(s))throw new Error(`${t} is invalid (${s})`);return s};class o{constructor(){this.i=1,this.P={}}D(s,i,r,e){return this.P[s]?this.P[s].i++:this.P[s]=new t(i,r,e,s),this.P[s]}k(){return Object.keys(this.P).map((t=>this.P[t]))}B(t){if(void 0===this.H){const s=this.k().map((s=>s.i/t));s.sort(((t,s)=>s-t)),this.H=s[0]||0}return this.H}I(t){const s=this.k();return s.sort(((s,i)=>i.i/t-s.i/t)),s[0]}R(){return this.k().reduce(((t,s)=>t.i>=s.i?t:s))}}class a{constructor(){this.i=1,this.P={}}V(t){return this.P[t]?this.P[t].i++:this.P[t]=new a,this.P[t]}k(){return Object.keys(this.P).map((t=>this.P[t]))}q(t){return this.P[t]?this.P[t].i++:this.P[t]=new o,this.P[t]}B(t){if(void 0===this.H){const s=this.k().map((s=>s.i/t));s.sort(((t,s)=>s-t)),this.H=s[0]||0}return this.H}I(t){const s=this.k();return s.sort(((s,i)=>i.i/t-s.i/t)),s[0].I(t)}R(){return this.k().reduce(((t,s)=>t.R().i>=s.R().i?t:s)).R()}A(s,i){const r=this.k().map((t=>{const{i:s}=t,i=t.R();return i.i=s,i}));r.sort(((t,s)=>s.i/i-t.i/i));const e=[];return r.forEach((i=>{const r=e.find((r=>t.distance(r,i)<s));r?r.i+=i.i:e.push(i)})),e}}const u=(t,s,i,e,h)=>{const n=((t,s,i,e,h)=>{const n=new r(i,e,h);t.forEach((t=>n.D(t)));const o=n.O();return o.sort(((t,i)=>(i.j+.1)*(.9-i.i/s)-(t.j+.1)*(.9-t.i/s))),o})(t,s,i,e,h);return n.map((t=>((t,s)=>({hex:`#${"0".repeat(6-t.p.toString(16).length)}${t.p.toString(16)}`,red:t.g,green:t.M,blue:t.C,area:t.i/s,hue:t.v,saturation:t.$,lightness:t._,intensity:t.j}))(t,s)))},c=(t,s={})=>{const[i,r,o,c,l,g,d]=(t=>{var s,i,r,o,a,u,c,l;return[e("pixels",null!=(s=t.pixels)?s:1e4,1),h("distance",null!=(i=t.distance)?i:.12,0,1),e("splitPower",null!=(r=t.splitPower)?r:10,2,15),n("colorValidator",null!=(o=t.colorValidator)?o:(t,s,i,r)=>(null!=r?r:255)>250),h("hueDistance",null!=(a=t.hueDistance)?a:.08333333333333333,0,1),h("saturationDistance",null!=(u=t.saturationDistance)?u:.2,0,1),h("lightnessDistance",null!=(c=t.lightnessDistance)?c:.2,0,1),null!=(l=t.crossOrigin)?l:null]})(s),M=(({data:t,width:s,height:i},r,e,h,n)=>{const o=new a,u=s&&i&&Math.floor(s*i/r)||1;for(let a=0;a<t.length;a+=4*u){const s=t[a],i=t[a+1],r=t[a+2];if(n(s,i,r,t[a+3])){const t=s<<16|i<<8|r,e=(s>>4&15)<<2|(i>>4&15)<<1|r>>4&15,n=Math.round(s*(h-1)/255)*(h*h)+Math.round(i*(h-1)/255)*h+Math.round(r*(h-1)/255);o.V(n).q(e).D(t,s,i,r)}}return o.A(e,r)})(t,i,r,o,c),w=t.width&&t.height?Math.min(t.width*t.height,i):i;return u(M,w,l,g,d)},l=(t,s)=>{if(t.data)return new Promise((i=>{i(c(t,s))}));throw new Error("Send imageData to extractColors")};exports.default=l,exports.extractColors=l,exports.extractColorsFromImage=()=>{throw new Error("Can not use extractColorsFromImage for Node.js")},exports.extractColorsFromImageData=c,exports.extractColorsFromSrc=()=>{throw new Error("Can not use extractColorsFromSrc for Node.js")}; | ||
//# sourceMappingURL=extract-colors.node.cjs.js.map |
@@ -0,12 +1,5 @@ | ||
import { extractColorsFromImageData } from './extractColors'; | ||
import { FinalColor } from './types/Color'; | ||
import type { NodeOptions } from "./types/Options"; | ||
/** | ||
* Extract colors from an ImageData object. | ||
*/ | ||
declare const extractColorsFromImageData: ({ data, width, height }: { | ||
data: Uint8ClampedArray | number[]; | ||
width?: number | undefined; | ||
height?: number | undefined; | ||
}, options?: NodeOptions) => FinalColor[]; | ||
/** | ||
* Extract colors from an imageData. | ||
@@ -13,0 +6,0 @@ */ |
@@ -1,53 +0,40 @@ | ||
const createFinalColor = (color, pixels) => { | ||
return { | ||
hex: `#${"0".repeat(6 - color.hex.toString(16).length)}${color.hex.toString(16)}`, | ||
red: color.red, | ||
green: color.green, | ||
blue: color.blue, | ||
area: color.count / pixels, | ||
hue: color.hue, | ||
saturation: color.saturation, | ||
lightness: color.lightness, | ||
intensity: color.intensity | ||
}; | ||
}; | ||
class Color { | ||
constructor(red, green, blue, hex = red << 16 | green << 8 | blue) { | ||
this.count = 1; | ||
this._saturation = -1; | ||
this._hue = -1; | ||
this._lightness = -1; | ||
this._intensity = -1; | ||
this.red = red; | ||
this.green = green; | ||
this.blue = blue; | ||
this.hex = hex; | ||
this._count = 1; | ||
this.__saturation = -1; | ||
this.__hue = -1; | ||
this.__lightness = -1; | ||
this.__intensity = -1; | ||
this._red = red; | ||
this._green = green; | ||
this._blue = blue; | ||
this._hex = hex; | ||
} | ||
static distance(colorA, colorB) { | ||
return (Math.abs(colorB.red - colorA.red) + Math.abs(colorB.green - colorA.green) + Math.abs(colorB.blue - colorA.blue)) / (3 * 255); | ||
return (Math.abs(colorB._red - colorA._red) + Math.abs(colorB._green - colorA._green) + Math.abs(colorB._blue - colorA._blue)) / (3 * 255); | ||
} | ||
updateHSL() { | ||
const red = this.red / 255; | ||
const green = this.green / 255; | ||
const blue = this.blue / 255; | ||
const red = this._red / 255; | ||
const green = this._green / 255; | ||
const blue = this._blue / 255; | ||
const max = Math.max(red, green, blue); | ||
const min = Math.min(red, green, blue); | ||
this._lightness = (max + min) / 2; | ||
this.__lightness = (max + min) / 2; | ||
if (max === min) { | ||
this._hue = 0; | ||
this._saturation = 0; | ||
this._intensity = 0; | ||
this.__hue = 0; | ||
this.__saturation = 0; | ||
this.__intensity = 0; | ||
} else { | ||
const distance2 = max - min; | ||
this._saturation = this._lightness > 0.5 ? distance2 / (2 - max - min) : distance2 / (max + min); | ||
this._intensity = this._saturation * ((0.5 - Math.abs(0.5 - this._lightness)) * 2); | ||
this.__saturation = this.__lightness > 0.5 ? distance2 / (2 - max - min) : distance2 / (max + min); | ||
this.__intensity = this.__saturation * ((0.5 - Math.abs(0.5 - this.__lightness)) * 2); | ||
switch (max) { | ||
case red: | ||
this._hue = ((green - blue) / distance2 + (green < blue ? 6 : 0)) / 6; | ||
this.__hue = ((green - blue) / distance2 + (green < blue ? 6 : 0)) / 6; | ||
break; | ||
case green: | ||
this._hue = ((blue - red) / distance2 + 2) / 6; | ||
this.__hue = ((blue - red) / distance2 + 2) / 6; | ||
break; | ||
case blue: | ||
this._hue = ((red - green) / distance2 + 4) / 6; | ||
this.__hue = ((red - green) / distance2 + 4) / 6; | ||
break; | ||
@@ -57,55 +44,171 @@ } | ||
} | ||
get hue() { | ||
if (this._hue === -1) { | ||
get _hue() { | ||
if (this.__hue === -1) { | ||
this.updateHSL(); | ||
} | ||
return this._hue; | ||
return this.__hue; | ||
} | ||
get saturation() { | ||
if (this._saturation === -1) { | ||
get _saturation() { | ||
if (this.__saturation === -1) { | ||
this.updateHSL(); | ||
} | ||
return this._saturation; | ||
return this.__saturation; | ||
} | ||
get lightness() { | ||
if (this._lightness === -1) { | ||
get _lightness() { | ||
if (this.__lightness === -1) { | ||
this.updateHSL(); | ||
} | ||
return this._lightness; | ||
return this.__lightness; | ||
} | ||
get intensity() { | ||
if (this._intensity === -1) { | ||
get _intensity() { | ||
if (this.__intensity === -1) { | ||
this.updateHSL(); | ||
} | ||
return this._intensity; | ||
return this.__intensity; | ||
} | ||
} | ||
const distance = (a, b) => Math.abs(a - b); | ||
const hueDistance = (a, b) => Math.min(distance(a, b), distance((a + 0.5) % 1, (b + 0.5) % 1)); | ||
class AverageGroup { | ||
constructor() { | ||
this.colors = []; | ||
this._average = null; | ||
} | ||
addColor(color) { | ||
this.colors.push(color); | ||
this._average = null; | ||
} | ||
isSamePalette(color, hue, saturation, lightness) { | ||
for (let i = 0; i < this.colors.length; i++) { | ||
const currentColor = this.colors[i]; | ||
const isSame = hueDistance(currentColor._hue, color._hue) < hue && distance(currentColor._saturation, color._saturation) < saturation && distance(currentColor._lightness, color._lightness) < lightness; | ||
if (!isSame) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
get average() { | ||
if (!this._average) { | ||
const { r, g, b } = this.colors.reduce((total2, color) => { | ||
total2.r += color._red; | ||
total2.g += color._green; | ||
total2.b += color._blue; | ||
return total2; | ||
}, { r: 0, g: 0, b: 0 }); | ||
const total = this.colors.reduce((_count, color) => _count + color._count, 0); | ||
this._average = new Color( | ||
Math.round(r / this.colors.length), | ||
Math.round(g / this.colors.length), | ||
Math.round(b / this.colors.length) | ||
); | ||
this._average._count = total; | ||
} | ||
return this._average; | ||
} | ||
} | ||
class AverageManager { | ||
constructor(hue, saturation, lightness) { | ||
this._groups = []; | ||
this._hue = hue; | ||
this._saturation = saturation; | ||
this._lightness = lightness; | ||
} | ||
addColor(color) { | ||
const samePalette = this._groups.find((averageGroup) => averageGroup.isSamePalette(color, this._hue, this._saturation, this._lightness)); | ||
if (samePalette) { | ||
samePalette.addColor(color); | ||
} else { | ||
const averageGroup = new AverageGroup(); | ||
averageGroup.addColor(color); | ||
this._groups.push(averageGroup); | ||
} | ||
} | ||
getGroups() { | ||
return this._groups.map((averageGroup) => averageGroup.average); | ||
} | ||
} | ||
var sortColors = (list, _pixels, _hueDistance, _saturationDistance, _lightnessDistance) => { | ||
const averageManager = new AverageManager(_hueDistance, _saturationDistance, _lightnessDistance); | ||
list.forEach((color) => averageManager.addColor(color)); | ||
const sorted = averageManager.getGroups(); | ||
sorted.sort((a, b) => { | ||
const bPower = (b._intensity + 0.1) * (0.9 - b._count / _pixels); | ||
const aPower = (a._intensity + 0.1) * (0.9 - a._count / _pixels); | ||
return bPower - aPower; | ||
}); | ||
return sorted; | ||
}; | ||
const createFinalColor = (color, pixels) => { | ||
return { | ||
hex: `#${"0".repeat(6 - color._hex.toString(16).length)}${color._hex.toString(16)}`, | ||
red: color._red, | ||
green: color._green, | ||
blue: color._blue, | ||
area: color._count / pixels, | ||
hue: color._hue, | ||
saturation: color._saturation, | ||
lightness: color._lightness, | ||
intensity: color._intensity | ||
}; | ||
}; | ||
const testUint = (label, val, min = 0, max = Number.MAX_SAFE_INTEGER) => { | ||
if (!Number.isInteger(val) || val < min || val > max) { | ||
throw new Error(`${label} is invalid (${val})`); | ||
} | ||
return val; | ||
}; | ||
const testNumber = (label, val, min = 0, max = Number.MAX_VALUE) => { | ||
if (Number(val) !== val || val < min || val > max) { | ||
throw new Error(`${label} is invalid (${val})`); | ||
} | ||
return val; | ||
}; | ||
const testFunction = (label, val) => { | ||
if (!val || {}.toString.call(val) !== "[object Function]") { | ||
throw new Error(`${label} is invalid (${val})`); | ||
} | ||
return val; | ||
}; | ||
var cleanInputs = (options) => { | ||
var _a, _b, _c, _d, _e, _f, _g, _h; | ||
return [ | ||
testUint("pixels", (_a = options.pixels) != null ? _a : 1e4, 1), | ||
testNumber("distance", (_b = options.distance) != null ? _b : 0.12, 0, 1), | ||
testUint("splitPower", (_c = options.splitPower) != null ? _c : 10, 2, 15), | ||
testFunction("colorValidator", (_d = options.colorValidator) != null ? _d : (_red, _green, _blue, _alpha) => (_alpha != null ? _alpha : 255) > 250), | ||
testNumber("hueDistance", (_e = options.hueDistance) != null ? _e : 0.08333333333333333, 0, 1), | ||
testNumber("saturationDistance", (_f = options.saturationDistance) != null ? _f : 0.2, 0, 1), | ||
testNumber("lightnessDistance", (_g = options.lightnessDistance) != null ? _g : 0.2, 0, 1), | ||
(_h = options.crossOrigin) != null ? _h : null | ||
]; | ||
}; | ||
class BudGroup { | ||
constructor() { | ||
this.count = 1; | ||
this.children = {}; | ||
this._count = 1; | ||
this._children = {}; | ||
} | ||
addColor(hex, red, green, blue) { | ||
if (this.children[hex]) { | ||
this.children[hex].count++; | ||
addColor(_hex, _red, _green, _blue) { | ||
if (this._children[_hex]) { | ||
this._children[_hex]._count++; | ||
} else { | ||
this.children[hex] = new Color(red, green, blue, hex); | ||
this._children[_hex] = new Color(_red, _green, _blue, _hex); | ||
} | ||
return this.children[hex]; | ||
return this._children[_hex]; | ||
} | ||
getList() { | ||
return Object.keys(this.children).map((key) => this.children[key]); | ||
return Object.keys(this._children).map((key) => this._children[key]); | ||
} | ||
getMaxWeight(count) { | ||
if (this.maxWeight === void 0) { | ||
const list = this.getList().map((child) => child.count / count); | ||
getMaxWeight(_count) { | ||
if (this._maxWeight === void 0) { | ||
const list = this.getList().map((child) => child._count / _count); | ||
list.sort((a, b) => b - a); | ||
this.maxWeight = list[0] || 0; | ||
this._maxWeight = list[0] || 0; | ||
} | ||
return this.maxWeight; | ||
return this._maxWeight; | ||
} | ||
getMaxWeightColor(count) { | ||
getMaxWeightColor(_count) { | ||
const list = this.getList(); | ||
list.sort((a, b) => { | ||
return b.count / count - a.count / count; | ||
return b._count / _count - a._count / _count; | ||
}); | ||
@@ -116,3 +219,3 @@ return list[0]; | ||
const list = this.getList(); | ||
const biggest = list.reduce((a, b) => a.count >= b.count ? a : b); | ||
const biggest = list.reduce((a, b) => a._count >= b._count ? a : b); | ||
return biggest; | ||
@@ -123,60 +226,59 @@ } | ||
constructor() { | ||
this.isColor = false; | ||
this.count = 1; | ||
this.children = {}; | ||
this._count = 1; | ||
this._children = {}; | ||
} | ||
addRootGroup(key) { | ||
if (this.children[key]) { | ||
this.children[key].count++; | ||
if (this._children[key]) { | ||
this._children[key]._count++; | ||
} else { | ||
this.children[key] = new RootGroup(); | ||
this._children[key] = new RootGroup(); | ||
} | ||
return this.children[key]; | ||
return this._children[key]; | ||
} | ||
getList() { | ||
return Object.keys(this.children).map((key) => this.children[key]); | ||
return Object.keys(this._children).map((key) => this._children[key]); | ||
} | ||
addBudGroup(key) { | ||
if (this.children[key]) { | ||
this.children[key].count++; | ||
if (this._children[key]) { | ||
this._children[key]._count++; | ||
} else { | ||
this.children[key] = new BudGroup(); | ||
this._children[key] = new BudGroup(); | ||
} | ||
return this.children[key]; | ||
return this._children[key]; | ||
} | ||
getMaxWeight(count) { | ||
if (this.maxWeight === void 0) { | ||
const list = this.getList().map((child) => child.count / count); | ||
getMaxWeight(_count) { | ||
if (this._maxWeight === void 0) { | ||
const list = this.getList().map((child) => child._count / _count); | ||
list.sort((a, b) => b - a); | ||
this.maxWeight = list[0] || 0; | ||
this._maxWeight = list[0] || 0; | ||
} | ||
return this.maxWeight; | ||
return this._maxWeight; | ||
} | ||
getMaxWeightColor(count) { | ||
getMaxWeightColor(_count) { | ||
const list = this.getList(); | ||
list.sort((a, b) => { | ||
return b.count / count - a.count / count; | ||
return b._count / _count - a._count / _count; | ||
}); | ||
return list[0].getMaxWeightColor(count); | ||
return list[0].getMaxWeightColor(_count); | ||
} | ||
getMaxCountColor() { | ||
const list = this.getList(); | ||
const biggest = list.reduce((a, b) => a.getMaxCountColor().count >= b.getMaxCountColor().count ? a : b); | ||
const biggest = list.reduce((a, b) => a.getMaxCountColor()._count >= b.getMaxCountColor()._count ? a : b); | ||
return biggest.getMaxCountColor(); | ||
} | ||
getColors(distance2, count) { | ||
getColors(_distance, _count) { | ||
const list = this.getList().map((child) => { | ||
const { count: count2 } = child; | ||
const { _count: _count2 } = child; | ||
const color = child.getMaxCountColor(); | ||
color.count = count2; | ||
color._count = _count2; | ||
return color; | ||
}); | ||
list.sort((a, b) => b.count / count - a.count / count); | ||
list.sort((a, b) => b._count / _count - a._count / _count); | ||
const newList = []; | ||
list.forEach((color) => { | ||
const near = newList.find((col) => Color.distance(col, color) < distance2); | ||
const near = newList.find((col) => Color.distance(col, color) < _distance); | ||
if (!near) { | ||
newList.push(color); | ||
} else { | ||
near.count += color.count; | ||
near._count += color._count; | ||
} | ||
@@ -187,152 +289,31 @@ }); | ||
} | ||
const testUint = (label, val, min = 0, max = Number.MAX_SAFE_INTEGER) => { | ||
if (!Number.isInteger(val) || val < min || val > max) { | ||
throw new Error(`${label} is invalid (${val})`); | ||
} | ||
return val; | ||
}; | ||
const testNumber = (label, val, min = 0, max = Number.MAX_VALUE) => { | ||
if (Number(val) !== val || val < min || val > max) { | ||
throw new Error(`${label} is invalid (${val})`); | ||
} | ||
return val; | ||
}; | ||
const testFunction = (label, val) => { | ||
if (!val || {}.toString.call(val) !== "[object Function]") { | ||
throw new Error(`${label} is invalid (${val})`); | ||
} | ||
return val; | ||
}; | ||
const _Extractor = class { | ||
constructor({ | ||
pixels = _Extractor.pixelsDefault, | ||
distance: distance2 = _Extractor.distanceDefault, | ||
splitPower = _Extractor.splitPowerDefault, | ||
colorValidator = _Extractor.colorValidatorDefault | ||
} = {}) { | ||
var _a, _b, _c, _d; | ||
this.pixels = (_a = testUint("pixels", pixels, 1)) != null ? _a : _Extractor.pixelsDefault; | ||
this.splitPower = (_b = testNumber("splitPower", splitPower, 2, 15)) != null ? _b : _Extractor.splitPowerDefault; | ||
this.distance = (_c = testNumber("distance", distance2, 0, 1)) != null ? _c : _Extractor.distanceDefault; | ||
this.colorValidator = (_d = testFunction("colorValidator", colorValidator)) != null ? _d : _Extractor.colorValidatorDefault; | ||
} | ||
process({ data, width, height }) { | ||
const rootGroup = new RootGroup(); | ||
const acc = this.splitPower; | ||
const reducer = width && height ? Math.floor(Math.sqrt(width * height) / this.pixels) || 1 : 1; | ||
for (let i = 0; i < data.length; i += 4 * reducer) { | ||
const r = data[i]; | ||
const g = data[i + 1]; | ||
const b = data[i + 2]; | ||
const a = data[i + 3]; | ||
if (this.colorValidator(r, g, b, a)) { | ||
const real = r << 16 | g << 8 | b; | ||
const medium = (r >> 4 & 15) << 2 | (g >> 4 & 15) << 1 | b >> 4 & 15; | ||
const small = Math.round(r * (acc - 1) / 255) * (acc * acc) + Math.round(g * (acc - 1) / 255) * acc + Math.round(b * (acc - 1) / 255); | ||
const smallGroup = rootGroup.addRootGroup(small); | ||
const mediumGroup = smallGroup.addBudGroup(medium); | ||
mediumGroup.addColor(real, r, g, b); | ||
} | ||
var extractor = ({ data, width, height }, _pixels, _distance, _splitPower, _colorValidator) => { | ||
const rootGroup = new RootGroup(); | ||
const reducer = width && height ? Math.floor(width * height / _pixels) || 1 : 1; | ||
for (let i = 0; i < data.length; i += 4 * reducer) { | ||
const r = data[i]; | ||
const g = data[i + 1]; | ||
const b = data[i + 2]; | ||
const a = data[i + 3]; | ||
if (_colorValidator(r, g, b, a)) { | ||
const real = r << 16 | g << 8 | b; | ||
const medium = (r >> 4 & 15) << 2 | (g >> 4 & 15) << 1 | b >> 4 & 15; | ||
const small = Math.round(r * (_splitPower - 1) / 255) * (_splitPower * _splitPower) + Math.round(g * (_splitPower - 1) / 255) * _splitPower + Math.round(b * (_splitPower - 1) / 255); | ||
const smallGroup = rootGroup.addRootGroup(small); | ||
const mediumGroup = smallGroup.addBudGroup(medium); | ||
mediumGroup.addColor(real, r, g, b); | ||
} | ||
return rootGroup.getColors(this.distance, this.pixels); | ||
} | ||
return rootGroup.getColors(_distance, _pixels); | ||
}; | ||
let Extractor = _Extractor; | ||
Extractor.pixelsDefault = 1e4; | ||
Extractor.distanceDefault = 0.12; | ||
Extractor.splitPowerDefault = 10; | ||
Extractor.colorValidatorDefault = (_red, _green, _blue, alpha) => (alpha != null ? alpha : 255) > 250; | ||
const distance = (a, b) => Math.abs(a - b); | ||
const hueDistance = (a, b) => Math.min(distance(a, b), distance((a + 0.5) % 1, (b + 0.5) % 1)); | ||
class AverageGroup { | ||
constructor() { | ||
this.colors = []; | ||
this._average = null; | ||
} | ||
addColor(color) { | ||
this.colors.push(color); | ||
this._average = null; | ||
} | ||
isSamePalette(color, hue, saturation, lightness) { | ||
for (let i = 0; i < this.colors.length; i++) { | ||
const currentColor = this.colors[i]; | ||
const isSame = hueDistance(currentColor.hue, color.hue) < hue && distance(currentColor.saturation, color.saturation) < saturation && distance(currentColor.lightness, color.lightness) < lightness; | ||
if (!isSame) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
get average() { | ||
if (!this._average) { | ||
const { red, green, blue } = this.colors.reduce((total2, color) => { | ||
total2.red += color.red; | ||
total2.green += color.green; | ||
total2.blue += color.blue; | ||
return total2; | ||
}, { red: 0, green: 0, blue: 0 }); | ||
const total = this.colors.reduce((count, color) => count + color.count, 0); | ||
this._average = new Color( | ||
Math.round(red / this.colors.length), | ||
Math.round(green / this.colors.length), | ||
Math.round(blue / this.colors.length) | ||
); | ||
this._average.count = total; | ||
} | ||
return this._average; | ||
} | ||
} | ||
const _AverageManager = class { | ||
constructor({ | ||
hue = _AverageManager.hueDefault, | ||
saturation = _AverageManager.saturationDefault, | ||
lightness = _AverageManager.lightnessDefault | ||
} = {}) { | ||
this._groups = []; | ||
this.hue = hue; | ||
this.saturation = saturation; | ||
this.lightness = lightness; | ||
} | ||
addColor(color) { | ||
const samePalette = this._groups.find((averageGroup) => averageGroup.isSamePalette(color, this.hue, this.saturation, this.lightness)); | ||
if (samePalette) { | ||
samePalette.addColor(color); | ||
} else { | ||
const averageGroup = new AverageGroup(); | ||
averageGroup.addColor(color); | ||
this._groups.push(averageGroup); | ||
} | ||
} | ||
getGroups() { | ||
return this._groups.map((averageGroup) => averageGroup.average); | ||
} | ||
const sortFinalColors = (_colors, _pixels, _hueDistance, _saturationDistance, _lightnessDistance) => { | ||
const list = sortColors(_colors, _pixels, _hueDistance, _saturationDistance, _lightnessDistance); | ||
return list.map((color) => createFinalColor(color, _pixels)); | ||
}; | ||
let AverageManager = _AverageManager; | ||
AverageManager.hueDefault = 1 / 12; | ||
AverageManager.saturationDefault = 1 / 5; | ||
AverageManager.lightnessDefault = 1 / 5; | ||
var sortColors = (list, pixels, { | ||
saturationDistance, | ||
lightnessDistance, | ||
hueDistance: hueDistance2 | ||
} = {}) => { | ||
const averageManager = new AverageManager({ hue: hueDistance2, saturation: saturationDistance, lightness: lightnessDistance }); | ||
list.forEach((color) => averageManager.addColor(color)); | ||
const sorted = averageManager.getGroups(); | ||
sorted.sort((a, b) => { | ||
const bPower = (b.intensity + 0.1) * (0.9 - b.count / pixels); | ||
const aPower = (a.intensity + 0.1) * (0.9 - a.count / pixels); | ||
return bPower - aPower; | ||
}); | ||
return sorted; | ||
const extractColorsFromImageData = (imageData, options = {}) => { | ||
const [_pixels, _distance, _splitPower, _colorValidator, _hueDistance, _saturationDistance, _lightnessDistance] = cleanInputs(options); | ||
const colors = extractor(imageData, _pixels, _distance, _splitPower, _colorValidator); | ||
const px = imageData.width && imageData.height ? Math.min(imageData.width * imageData.height, _pixels) : _pixels; | ||
return sortFinalColors(colors, px, _hueDistance, _saturationDistance, _lightnessDistance); | ||
}; | ||
const sortFinalColors = (colors, pixels, options) => { | ||
const list = sortColors(colors, pixels, options); | ||
return list.map((color) => createFinalColor(color, pixels)); | ||
}; | ||
const extractColorsFromImageData = ({ data, width = Number.MAX_SAFE_INTEGER, height = Number.MAX_SAFE_INTEGER }, options) => { | ||
const extractor = new Extractor(options); | ||
const colors = extractor.process({ data, width, height }); | ||
return sortFinalColors(colors, extractor.pixels, options); | ||
}; | ||
const extractColors = (imageData, options) => { | ||
@@ -339,0 +320,0 @@ if (imageData.data) { |
import Color from "../color/Color"; | ||
export declare class AverageManager { | ||
hue: number; | ||
saturation: number; | ||
lightness: number; | ||
_hue: number; | ||
_saturation: number; | ||
_lightness: number; | ||
private _groups; | ||
static hueDefault: number; | ||
static saturationDefault: number; | ||
static lightnessDefault: number; | ||
constructor({ hue, saturation, lightness }?: { | ||
hue?: number | undefined; | ||
saturation?: number | undefined; | ||
lightness?: number | undefined; | ||
}); | ||
constructor(hue: number, saturation: number, lightness: number); | ||
addColor(color: Color): void; | ||
getGroups(): Color[]; | ||
} |
import Color from "../color/Color"; | ||
import { SorterOptions } from "../types/Options"; | ||
declare const _default: (list: Color[], pixels: number, { saturationDistance, lightnessDistance, hueDistance }?: SorterOptions) => Color[]; | ||
declare const _default: (list: Color[], _pixels: number, _hueDistance: number, _saturationDistance: number, _lightnessDistance: number) => Color[]; | ||
export default _default; |
{ | ||
"name": "extract-colors", | ||
"version": "2.0.3", | ||
"version": "2.0.4", | ||
"description": "Extract color palettes from images", | ||
@@ -13,2 +13,3 @@ "main": "lib/extract-colors.node.cjs.js", | ||
"lint": "eslint src --fix", | ||
"pretest": "npm run build", | ||
"test": "vitest --config conf/vite.config.test.ts", | ||
@@ -15,0 +16,0 @@ "coverage": "vitest run --coverage --config conf/vite.config.test.ts", |
@@ -14,3 +14,3 @@ # Extract Colors | ||
Extract color palettes from images. | ||
Simple use, < 7ko minified, fast process and no dependencies for browser. | ||
Simple use, < 6ko minified, fast process and no dependencies for browser. | ||
Need image reader for node.js | ||
@@ -38,3 +38,3 @@ | ||
- Node.js: 0.12+ | ||
- Node.js: 6.0+ | ||
@@ -82,3 +82,3 @@ | ||
const getPixels = require("get-pixels") | ||
const extractColors = require('extract-colors') | ||
const { extractColors } = require('extract-colors') | ||
@@ -100,3 +100,4 @@ const src = path.join(__dirname, './my-image.jpg') | ||
> You can use different types for `src` param (`String` for a path of image or `ImageData`). | ||
> This example use `get-pixels` but you can change the lib. | ||
> Just send and ImageData object to `extractColors(imageData)`. | ||
@@ -110,3 +111,2 @@ | ||
distance: 0.2, | ||
saturationImportance: 0.2, | ||
splitPower: 10, | ||
@@ -113,0 +113,0 @@ colorValidator: (red, green, blue, alpha = 255) => alpha > 250, |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
198976
27
1040
1