img-color-extractor
Version:
Extract main colors from png
138 lines (111 loc) • 3.65 kB
JavaScript
const Helpers = function () {};
/**
* Transform a base 10 value to its base 16 value
*
* @param c - base 10 value
* @returns {string} - the base 16 value
*/
Helpers.prototype.componentToHex = function (c) {
const hex = c.toString(16);
return hex.length == 1 ? '0' + hex : hex;
};
/**
* Transform base 10 r g b color il its hex code
*
* @param r - base 10 red component value
* @param g - base 10 green component value
* @param b - base 10 blue component value
* @returns {string} - the hex value
*/
Helpers.prototype.rgbToHex = function (r, g, b) {
return '#' + this.componentToHex(r) + this.componentToHex(g) + this.componentToHex(b);
};
/**
* Transform hex color in its base 10 r g b components
*
* @param hex - hex color (i.e. #F3306B)
* @returns {Object} - the base 10 r g b color (i.e. { r: 243, g: 48, b: 107 })
*/
Helpers.prototype.hexToRgb = function (hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
} : null;
};
/**
* Filter and order colorsArrStr
*
* Remove shades of gray: if variance(r, g b) > opts.greyVa(=100)
* Merge close colorsArrStr:
* 1. sort colorsArrStr by occurrence
* 2. iterate the colorsArrStr starting by the most common -> c1
* iterate the other and less common colorsArrStr -> c2
* if distance(c1, c2) < opts.dist(=100)
* c2 <- c1
*
* @param colorsArrStr {string[]} - array of hex colorsArrStr
* @param opts {Object=} - options
* @returns {Array} - the filtered and ordered colors (e.g. [{ color: `hexa`, n: `numberOfOccurrence`, r: `ratio` }, ...]
*/
Helpers.prototype.filter = function (colorsArrStr, opts) {
opts = opts || {};
const options = {
dist: opts.dist || 100,
greyVa: opts.greyVa || -1,
};
let countsObj = {};
let colorsArrObj = [];
for (let c of colorsArrStr) {
if (!/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(c))
continue;
if (options.greyVa >= 0) {
const rgb = this.hexToRgb(c);
const moy = (rgb.r + rgb.g + rgb.b) / 3;
const va = ((rgb.r - moy) * (rgb.r - moy) + (rgb.g - moy) * (rgb.g - moy) + (rgb.b - moy) * (rgb.b - moy)) / 3;
if (va <= options.greyVa)
continue;
}
countsObj[c] = countsObj[c] ? countsObj[c] + 1 : 1;
}
for (let c in countsObj) {
colorsArrObj.push({
color: c,
n: countsObj[c],
});
}
colorsArrObj.sort((a, b) => b.n - a.n);
countsObj = {};
let total = 0;
for (let i = 0; i < colorsArrObj.length; i++) {
if (typeof colorsArrObj[i].color === 'undefined')
continue;
countsObj[colorsArrObj[i].color] = colorsArrObj[i].n;
let n = 0;
const rgb = this.hexToRgb(colorsArrObj[i].color);
for (let j = i + 1; j < colorsArrObj.length; j++) {
if (typeof colorsArrObj[j].color === 'undefined')
continue;
const rgbNext = this.hexToRgb(colorsArrObj[j].color);
const dist = Math.sqrt((rgbNext.r - rgb.r) * (rgbNext.r - rgb.r) + (rgbNext.g - rgb.g) * (rgbNext.g - rgb.g) + (rgbNext.b - rgb.b) * (rgbNext.b - rgb.b));
if (dist < options.dist) {
n += colorsArrObj[j].n;
delete colorsArrObj[j].color;
}
}
countsObj[colorsArrObj[i].color] += n;
total += countsObj[colorsArrObj[i].color];
}
const colorsArrObjRet = [];
for (let c in countsObj) {
colorsArrObjRet.push({
color: c,
n: countsObj[c],
r: Number((countsObj[c] / total).toFixed(10)),
});
}
return colorsArrObjRet.sort((a, b) => b.n - a.n);
};
module.exports = new Helpers();
;