colorgram
Version:
Color extraction library. In TypeScript/JavaScript. For browser/server.
163 lines (160 loc) • 4.63 kB
JavaScript
/**
* RGB = 3, RGBAlpha = 4
* TODO: Alpha not calculated
* TODO: Grey not supported
*/
var Channels;
(function (Channels) {
// Grey = 1,
// GreyAlpha = 2,
Channels[Channels["RGB"] = 3] = "RGB";
Channels[Channels["RGBAlpha"] = 4] = "RGBAlpha";
})(Channels || (Channels = {}));
function extract(img, top) {
if (top === void 0) { top = 12; }
var samples = sample(img);
var used = pickUsed(samples, samples.length / 4);
sortUsed(used);
// TODO: Random idea: group similar by HSL (or HUSL?) distance
return getColorStats(samples, used, top);
}
function sample(img) {
var topBits = 2;
/* tslint:disable:no-bitwise */
var sides = 1 << topBits; // 4
/* tslint:enable:no-bitwise */
var shiftM = 2;
var shiftH = 4;
var shiftL = 6;
var mask = 192;
var cubes = Math.pow(sides, 3);
// 4 is RGB + Count
var samples = new Uint32Array(cubes * 4);
for (var i = 0; i < img.data.length; i += img.channels) {
var h = hsl(img.data[i], img.data[i + 1], img.data[i + 2]);
/* tslint:disable:no-bitwise */
var v = ((~~(img.data[i] * 0.2126 + img.data[i + 1] * 0.7152 + img.data[i + 2] * 0.0722) & mask) >> shiftM) +
((h[0] & mask) >> shiftH) +
((h[2] & mask) >> shiftL);
/* tslint:enable:no-bitwise */
// 4 is RGB + Count
v *= 4;
samples[v++] += img.data[i];
samples[v++] += img.data[i + 1];
samples[v++] += img.data[i + 2];
samples[v]++;
}
return samples;
}
function hsl(r, g, b) {
var max = Math.max(r, g, b);
var min = Math.min(r, g, b);
/* tslint:disable:no-bitwise */
var h = 0;
var s;
var l = (max + min) >> 1;
/* tslint:enable:no-bitwise */
if (max === min) {
s = 0;
}
else {
var d = max - min;
s = l > 127 ? d / (510 - max - min) : d / (max + min);
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
default:
break;
}
h /= 6;
}
return [h * 255, s * 255, l];
}
/**
* TODO: sorted only by H
*/
function sortByHsl(stats) {
var i;
for (i = 0; i < stats.length; i++) {
stats[i][4] = hsl(stats[i][0], stats[i][1], stats[i][2]);
}
stats.sort(function (a, b) {
return b[4][0] - a[4][0];
});
for (i = 0; i < stats.length; i++) {
delete stats[i][4];
}
return stats;
}
function pickUsed(samples, cubes) {
var used = [];
for (var j = 0; j < cubes; j++) {
var p = j * 4;
var s = samples[p + 3];
if (s) {
used.push([s, p]);
}
}
return used;
}
function sortUsed(used) {
used.sort(function (a, b) {
return b[0] - a[0];
});
}
function getColorStats(samples, used, top) {
var pixels = 0;
var stats = [];
var max = Math.min(top, used.length);
var i;
for (i = 0; i < max; i++) {
var count = used[i][0];
var p = used[i][1];
/* tslint:disable:no-bitwise */
var c = [~~(samples[p] / count), ~~(samples[p + 1] / count), ~~(samples[p + 2] / count), count];
/* tslint:enable:no-bitwise */
stats.push(c);
pixels += count;
}
for (i = 0; i < stats.length; i++) {
stats[i][3] /= pixels;
}
return stats;
}
function getPixels(stats, width, proportional, channels) {
if (proportional === void 0) { proportional = false; }
if (channels === void 0) { channels = Channels.RGBAlpha; }
var step;
var k;
var i;
var current;
var end;
var sum = 0;
var data = [];
for (i = 0; i < stats.length; i++) {
step = proportional ? stats[i][3] : 1 / stats.length;
current = Math.round(width * sum) * 4;
end = Math.round(width * (sum + step)) * 4;
sum += step;
k = current;
while (current < end) {
data[k++] = stats[i][0];
data[k++] = stats[i][1];
data[k++] = stats[i][2];
if (channels === Channels.RGBAlpha) {
data[k++] = 255;
}
current += channels;
}
}
return data;
}
export { Channels, extract, getPixels, hsl, sample, sortByHsl };
//# sourceMappingURL=index.js.map