386 lines
12 KiB
Groff
Executable File
386 lines
12 KiB
Groff
Executable File
/*!
|
|
* quantize.js Copyright 2008 Nick Rabinowitz.
|
|
* Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
|
|
*/
|
|
|
|
/*!
|
|
* Block below copied from Protovis: http://mbostock.github.com/protovis/
|
|
* Copyright 2010 Stanford Visualization Group
|
|
* Licensed under the BSD License: http://www.opensource.org/licenses/bsd-license.php
|
|
*
|
|
* Basic Javascript port of the MMCQ (modified median cut quantization)
|
|
* algorithm from the Leptonica library (http://www.leptonica.com/).
|
|
* Returns a color map you can use to map original pixels to the reduced
|
|
* palette. Still a work in progress.
|
|
*/
|
|
if (!pv) {
|
|
var pv = {
|
|
map: function (e, t) {
|
|
var n = {};
|
|
return t ? e.map(function (e, r) {
|
|
n.index = r;
|
|
return t.call(n, e)
|
|
}) : e.slice()
|
|
},
|
|
naturalOrder: function (e, t) {
|
|
return e < t ? -1 : e > t ? 1 : 0
|
|
},
|
|
sum: function (e, t) {
|
|
var n = {};
|
|
return e.reduce(t ? function (e, r, i) {
|
|
n.index = i;
|
|
return e + t.call(n, r)
|
|
} : function (e, t) {
|
|
return e + t
|
|
}, 0)
|
|
},
|
|
max: function (e, t) {
|
|
return Math.max.apply(null, t ? pv.map(e, t) : e)
|
|
}
|
|
}
|
|
}
|
|
var MMCQ = function () {
|
|
function i(t, n, r) {
|
|
return (t << 2 * e) + (n << e) + r
|
|
}
|
|
function s(e) {
|
|
function r() {
|
|
t.sort(e);
|
|
n = true
|
|
}
|
|
var t = [],
|
|
n = false;
|
|
return {
|
|
push: function (e) {
|
|
t.push(e);
|
|
n = false
|
|
},
|
|
peek: function (e) {
|
|
if (!n) r();
|
|
if (e === undefined) e = t.length - 1;
|
|
return t[e]
|
|
},
|
|
pop: function () {
|
|
if (!n) r();
|
|
return t.pop()
|
|
},
|
|
size: function () {
|
|
return t.length
|
|
},
|
|
map: function (e) {
|
|
return t.map(e)
|
|
},
|
|
debug: function () {
|
|
if (!n) r();
|
|
return t
|
|
}
|
|
}
|
|
}
|
|
function o(e, t, n, r, i, s, o) {
|
|
var u = this;
|
|
u.r1 = e;
|
|
u.r2 = t;
|
|
u.g1 = n;
|
|
u.g2 = r;
|
|
u.b1 = i;
|
|
u.b2 = s;
|
|
u.histo = o
|
|
}
|
|
function u() {
|
|
this.vboxes = new s(function (e, t) {
|
|
return pv.naturalOrder(e.vbox.count() * e.vbox.volume(), t.vbox.count() * t.vbox.volume())
|
|
});
|
|
}
|
|
function a(n) {
|
|
var r = 1 << 3 * e,
|
|
s = new Array(r),
|
|
o, u, a, f;
|
|
n.forEach(function (e) {
|
|
u = e[0] >> t;
|
|
a = e[1] >> t;
|
|
f = e[2] >> t;
|
|
o = i(u, a, f);
|
|
s[o] = (s[o] || 0) + 1
|
|
});
|
|
return s
|
|
}
|
|
function f(e, n) {
|
|
var r = 1e6,
|
|
i = 0,
|
|
s = 1e6,
|
|
u = 0,
|
|
a = 1e6,
|
|
f = 0,
|
|
l, c, h;
|
|
e.forEach(function (e) {
|
|
l = e[0] >> t;
|
|
c = e[1] >> t;
|
|
h = e[2] >> t;
|
|
if (l < r) r = l;
|
|
else if (l > i) i = l;
|
|
if (c < s) s = c;
|
|
else if (c > u) u = c;
|
|
if (h < a) a = h;
|
|
else if (h > f) f = h
|
|
});
|
|
return new o(r, i, s, u, a, f, n)
|
|
}
|
|
function l(e, t) {
|
|
function v(e) {
|
|
var n = e + "1",
|
|
r = e + "2",
|
|
i, s, o, c, h, p = 0;
|
|
for (l = t[n]; l <= t[r]; l++) {
|
|
if (a[l] > u / 2) {
|
|
o = t.copy();
|
|
c = t.copy();
|
|
i = l - t[n];
|
|
s = t[r] - l;
|
|
if (i <= s) h = Math.min(t[r] - 1, ~~ (l + s / 2));
|
|
else h = Math.max(t[n], ~~ (l - 1 - i / 2));
|
|
while (!a[h]) h++;
|
|
p = f[h];
|
|
while (!p && a[h - 1]) p = f[--h];
|
|
o[r] = h;
|
|
c[n] = o[r] + 1;
|
|
return [o, c]
|
|
}
|
|
}
|
|
}
|
|
if (!t.count()) return;
|
|
var n = t.r2 - t.r1 + 1,
|
|
r = t.g2 - t.g1 + 1,
|
|
s = t.b2 - t.b1 + 1,
|
|
o = pv.max([n, r, s]);
|
|
if (t.count() == 1) {
|
|
return [t.copy()]
|
|
}
|
|
var u = 0,
|
|
a = [],
|
|
f = [],
|
|
l, c, h, p, d;
|
|
if (o == n) {
|
|
for (l = t.r1; l <= t.r2; l++) {
|
|
p = 0;
|
|
for (c = t.g1; c <= t.g2; c++) {
|
|
for (h = t.b1; h <= t.b2; h++) {
|
|
d = i(l, c, h);
|
|
p += e[d] || 0
|
|
}
|
|
}
|
|
u += p;
|
|
a[l] = u
|
|
}
|
|
} else if (o == r) {
|
|
for (l = t.g1; l <= t.g2; l++) {
|
|
p = 0;
|
|
for (c = t.r1; c <= t.r2; c++) {
|
|
for (h = t.b1; h <= t.b2; h++) {
|
|
d = i(c, l, h);
|
|
p += e[d] || 0
|
|
}
|
|
}
|
|
u += p;
|
|
a[l] = u
|
|
}
|
|
} else {
|
|
for (l = t.b1; l <= t.b2; l++) {
|
|
p = 0;
|
|
for (c = t.r1; c <= t.r2; c++) {
|
|
for (h = t.g1; h <= t.g2; h++) {
|
|
d = i(c, h, l);
|
|
p += e[d] || 0
|
|
}
|
|
}
|
|
u += p;
|
|
a[l] = u
|
|
}
|
|
}
|
|
a.forEach(function (e, t) {
|
|
f[t] = u - e
|
|
});
|
|
return o == n ? v("r") : o == r ? v("g") : v("b")
|
|
}
|
|
function c(t, i) {
|
|
function v(e, t) {
|
|
var r = 1,
|
|
i = 0,
|
|
s;
|
|
while (i < n) {
|
|
s = e.pop();
|
|
if (!s.count()) {
|
|
e.push(s);
|
|
i++;
|
|
continue
|
|
}
|
|
var u = l(o, s),
|
|
a = u[0],
|
|
f = u[1];
|
|
if (!a) {
|
|
return
|
|
}
|
|
e.push(a);
|
|
if (f) {
|
|
e.push(f);
|
|
r++
|
|
}
|
|
if (r >= t) return;
|
|
if (i++ > n) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
if (!t.length || i < 2 || i > 256) {
|
|
return false
|
|
}
|
|
var o = a(t),
|
|
c = 1 << 3 * e;
|
|
var h = 0;
|
|
o.forEach(function () {
|
|
h++
|
|
});
|
|
if (h <= i) {}
|
|
var p = f(t, o),
|
|
d = new s(function (e, t) {
|
|
return pv.naturalOrder(e.count(), t.count())
|
|
});
|
|
d.push(p);
|
|
v(d, r * i);
|
|
var m = new s(function (e, t) {
|
|
return pv.naturalOrder(e.count() * e.volume(), t.count() * t.volume())
|
|
});
|
|
while (d.size()) {
|
|
m.push(d.pop())
|
|
}
|
|
v(m, i - m.size());
|
|
var g = new u;
|
|
while (m.size()) {
|
|
g.push(m.pop())
|
|
}
|
|
return g
|
|
}
|
|
var e = 5,
|
|
t = 8 - e,
|
|
n = 1e3,
|
|
r = .75;
|
|
o.prototype = {
|
|
volume: function (e) {
|
|
var t = this;
|
|
if (!t._volume || e) {
|
|
t._volume = (t.r2 - t.r1 + 1) * (t.g2 - t.g1 + 1) * (t.b2 - t.b1 + 1)
|
|
}
|
|
return t._volume
|
|
},
|
|
count: function (e) {
|
|
var t = this,
|
|
n = t.histo;
|
|
if (!t._count_set || e) {
|
|
var r = 0,
|
|
s, o, u;
|
|
for (s = t.r1; s <= t.r2; s++) {
|
|
for (o = t.g1; o <= t.g2; o++) {
|
|
for (u = t.b1; u <= t.b2; u++) {
|
|
index = i(s, o, u);
|
|
r += n[index] || 0
|
|
}
|
|
}
|
|
}
|
|
t._count = r;
|
|
t._count_set = true
|
|
}
|
|
return t._count
|
|
},
|
|
copy: function () {
|
|
var e = this;
|
|
return new o(e.r1, e.r2, e.g1, e.g2, e.b1, e.b2, e.histo)
|
|
},
|
|
avg: function (t) {
|
|
var n = this,
|
|
r = n.histo;
|
|
if (!n._avg || t) {
|
|
var s = 0,
|
|
o = 1 << 8 - e,
|
|
u = 0,
|
|
a = 0,
|
|
f = 0,
|
|
l, c, h, p, d;
|
|
for (c = n.r1; c <= n.r2; c++) {
|
|
for (h = n.g1; h <= n.g2; h++) {
|
|
for (p = n.b1; p <= n.b2; p++) {
|
|
d = i(c, h, p);
|
|
l = r[d] || 0;
|
|
s += l;
|
|
u += l * (c + .5) * o;
|
|
a += l * (h + .5) * o;
|
|
f += l * (p + .5) * o
|
|
}
|
|
}
|
|
}
|
|
if (s) {
|
|
n._avg = [~~(u / s), ~~ (a / s), ~~ (f / s)]
|
|
} else {
|
|
n._avg = [~~(o * (n.r1 + n.r2 + 1) / 2), ~~ (o * (n.g1 + n.g2 + 1) / 2), ~~ (o * (n.b1 + n.b2 + 1) / 2)]
|
|
}
|
|
}
|
|
return n._avg
|
|
},
|
|
contains: function (e) {
|
|
var n = this,
|
|
r = e[0] >> t;
|
|
gval = e[1] >> t;
|
|
bval = e[2] >> t;
|
|
return r >= n.r1 && r <= n.r2 && gval >= n.g1 && r <= n.g2 && bval >= n.b1 && r <= n.b2
|
|
}
|
|
};
|
|
u.prototype = {
|
|
push: function (e) {
|
|
this.vboxes.push({
|
|
vbox: e,
|
|
color: e.avg()
|
|
})
|
|
},
|
|
palette: function () {
|
|
return this.vboxes.map(function (e) {
|
|
return e.color
|
|
})
|
|
},
|
|
size: function () {
|
|
return this.vboxes.size()
|
|
},
|
|
map: function (e) {
|
|
var t = this.vboxes;
|
|
for (var n = 0; n < t.size(); n++) {
|
|
if (t.peek(n).vbox.contains(e)) {
|
|
return t.peek(n).color
|
|
}
|
|
}
|
|
return this.nearest(e)
|
|
},
|
|
nearest: function (e) {
|
|
var t = this.vboxes,
|
|
n, r, i;
|
|
for (var s = 0; s < t.size(); s++) {
|
|
r = Math.sqrt(Math.pow(e[0] - t.peek(s).color[0], 2) + Math.pow(e[1] - t.peek(s).color[1], 2) + Math.pow(e[1] - t.peek(s).color[1], 2));
|
|
if (r < n || n === undefined) {
|
|
n = r;
|
|
i = t.peek(s).color
|
|
}
|
|
}
|
|
return i
|
|
},
|
|
forcebw: function () {
|
|
var e = this.vboxes;
|
|
e.sort(function (e, t) {
|
|
return pv.naturalOrder(pv.sum(e.color), pv.sum(t.color))
|
|
});
|
|
var t = e[0].color;
|
|
if (t[0] < 5 && t[1] < 5 && t[2] < 5) e[0].color = [0, 0, 0];
|
|
var n = e.length - 1,
|
|
r = e[n].color;
|
|
if (r[0] > 251 && r[1] > 251 && r[2] > 251) e[n].color = [255, 255, 255]
|
|
}
|
|
};
|
|
return {
|
|
quantize: c
|
|
}
|
|
}() |