/*! * 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 } }()