This commit is contained in:
Azgaar 2018-03-29 21:58:21 +03:00
parent 13531112d3
commit 0e1dd80812
19 changed files with 810 additions and 119 deletions

2
libs/d3-hexbin.v0.2.min.js vendored Normal file
View file

@ -0,0 +1,2 @@
// https://github.com/d3/d3-hexbin Version 0.2.2. Copyright 2017 Mike Bostock.
!function(n,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t(n.d3=n.d3||{})}(this,function(n){"use strict";function t(n){return n[0]}function r(n){return n[1]}var e=Math.PI/3,u=[0,e,2*e,3*e,4*e,5*e],o=function(){function n(n){var t,r={},e=[],u=n.length;for(t=0;t<u;++t)if(!isNaN(i=+d.call(null,o=n[t],t,n))&&!isNaN(c=+p.call(null,o,t,n))){var o,i,c,s=Math.round(c/=f),h=Math.round(i=i/a-(1&s)/2),l=c-s;if(3*Math.abs(l)>1){var v=i-h,M=h+(i<h?-1:1)/2,x=s+(c<s?-1:1),g=i-M,m=c-x;v*v+l*l>g*g+m*m&&(h=M+(1&s?1:-1)/2,s=x)}var y=h+"-"+s,j=r[y];j?j.push(o):(e.push(j=r[y]=[o]),j.x=(h+(1&s)/2)*a,j.y=s*f)}return e}function o(n){var t=0,r=0;return u.map(function(e){var u=Math.sin(e)*n,o=-Math.cos(e)*n,i=u-t,a=o-r;return t=u,r=o,[i,a]})}var i,a,f,c=0,s=0,h=1,l=1,d=t,p=r;return n.hexagon=function(n){return"m"+o(null==n?i:+n).join("l")+"z"},n.centers=function(){for(var n=[],t=Math.round(s/f),r=Math.round(c/a),e=t*f;e<l+i;e+=f,++t)for(var u=r*a+(1&t)*a/2;u<h+a/2;u+=a)n.push([u,e]);return n},n.mesh=function(){var t=o(i).slice(0,4).join("l");return n.centers().map(function(n){return"M"+n+"m"+t}).join("")},n.x=function(t){return arguments.length?(d=t,n):d},n.y=function(t){return arguments.length?(p=t,n):p},n.radius=function(t){return arguments.length?(i=+t,a=2*i*Math.sin(e),f=1.5*i,n):i},n.size=function(t){return arguments.length?(c=s=0,h=+t[0],l=+t[1],n):[h-c,l-s]},n.extent=function(t){return arguments.length?(c=+t[0][0],s=+t[0][1],h=+t[1][0],l=+t[1][1],n):[[c,s],[h,l]]},n.radius(1)};n.hexbin=o,Object.defineProperty(n,"__esModule",{value:!0})});

2
libs/d3-scale-chromatic.v1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2
libs/d3.v4.min.js vendored Normal file

File diff suppressed because one or more lines are too long

4
libs/jquery-3.1.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

13
libs/jquery-ui.min.js vendored Normal file

File diff suppressed because one or more lines are too long

232
libs/polylabel.js Normal file
View file

@ -0,0 +1,232 @@
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.polylabel = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
'use strict';
var Queue = require('tinyqueue');
module.exports = polylabel;
function polylabel(polygon, precision, debug) {
precision = precision || 1.0;
// find the bounding box of the outer ring
var minX, minY, maxX, maxY;
for (var i = 0; i < polygon[0].length; i++) {
var p = polygon[0][i];
if (!i || p[0] < minX) minX = p[0];
if (!i || p[1] < minY) minY = p[1];
if (!i || p[0] > maxX) maxX = p[0];
if (!i || p[1] > maxY) maxY = p[1];
}
var width = maxX - minX;
var height = maxY - minY;
var cellSize = Math.min(width, height);
var h = cellSize / 2;
// a priority queue of cells in order of their "potential" (max distance to polygon)
var cellQueue = new Queue(null, compareMax);
// cover polygon with initial cells
for (var x = minX; x < maxX; x += cellSize) {
for (var y = minY; y < maxY; y += cellSize) {
cellQueue.push(new Cell(x + h, y + h, h, polygon));
}
}
// take centroid as the first best guess
var bestCell = getCentroidCell(polygon);
var numProbes = cellQueue.length;
while (cellQueue.length) {
// pick the most promising cell from the queue
var cell = cellQueue.pop();
// update the best cell if we found a better one
if (cell.d > bestCell.d) {
bestCell = cell;
if (debug) console.log('found best %d after %d probes', Math.round(1e4 * cell.d) / 1e4, numProbes);
}
// do not drill down further if there's no chance of a better solution
if (cell.max - bestCell.d <= precision) continue;
// split the cell into four cells
h = cell.h / 2;
cellQueue.push(new Cell(cell.x - h, cell.y - h, h, polygon));
cellQueue.push(new Cell(cell.x + h, cell.y - h, h, polygon));
cellQueue.push(new Cell(cell.x - h, cell.y + h, h, polygon));
cellQueue.push(new Cell(cell.x + h, cell.y + h, h, polygon));
numProbes += 4;
}
if (debug) {
console.log('num probes: ' + numProbes);
console.log('best distance: ' + bestCell.d);
}
return [bestCell.x, bestCell.y];
}
function compareMax(a, b) {
return b.max - a.max;
}
function Cell(x, y, h, polygon) {
this.x = x; // cell center x
this.y = y; // cell center y
this.h = h; // half the cell size
this.d = pointToPolygonDist(x, y, polygon); // distance from cell center to polygon
this.max = this.d + this.h * Math.SQRT2; // max distance to polygon within a cell
}
// signed distance from point to polygon outline (negative if point is outside)
function pointToPolygonDist(x, y, polygon) {
var inside = false;
var minDistSq = Infinity;
for (var k = 0; k < polygon.length; k++) {
var ring = polygon[k];
for (var i = 0, len = ring.length, j = len - 1; i < len; j = i++) {
var a = ring[i];
var b = ring[j];
if ((a[1] > y !== b[1] > y) &&
(x < (b[0] - a[0]) * (y - a[1]) / (b[1] - a[1]) + a[0])) inside = !inside;
minDistSq = Math.min(minDistSq, getSegDistSq(x, y, a, b));
}
}
return (inside ? 1 : -1) * Math.sqrt(minDistSq);
}
// get polygon centroid
function getCentroidCell(polygon) {
var area = 0;
var x = 0;
var y = 0;
var points = polygon[0];
for (var i = 0, len = points.length, j = len - 1; i < len; j = i++) {
var a = points[i];
var b = points[j];
var f = a[0] * b[1] - b[0] * a[1];
x += (a[0] + b[0]) * f;
y += (a[1] + b[1]) * f;
area += f * 3;
}
return new Cell(x / area, y / area, 0, polygon);
}
// get squared distance from a point to a segment
function getSegDistSq(px, py, a, b) {
var x = a[0];
var y = a[1];
var dx = b[0] - x;
var dy = b[1] - y;
if (dx !== 0 || dy !== 0) {
var t = ((px - x) * dx + (py - y) * dy) / (dx * dx + dy * dy);
if (t > 1) {
x = b[0];
y = b[1];
} else if (t > 0) {
x += dx * t;
y += dy * t;
}
}
dx = px - x;
dy = py - y;
return dx * dx + dy * dy;
}
},{"tinyqueue":2}],2:[function(require,module,exports){
'use strict';
module.exports = TinyQueue;
function TinyQueue(data, compare) {
if (!(this instanceof TinyQueue)) return new TinyQueue(data, compare);
this.data = data || [];
this.length = this.data.length;
this.compare = compare || defaultCompare;
if (data) for (var i = Math.floor(this.length / 2); i >= 0; i--) this._down(i);
}
function defaultCompare(a, b) {
return a < b ? -1 : a > b ? 1 : 0;
}
TinyQueue.prototype = {
push: function (item) {
this.data.push(item);
this.length++;
this._up(this.length - 1);
},
pop: function () {
var top = this.data[0];
this.data[0] = this.data[this.length - 1];
this.length--;
this.data.pop();
this._down(0);
return top;
},
peek: function () {
return this.data[0];
},
_up: function (pos) {
var data = this.data,
compare = this.compare;
while (pos > 0) {
var parent = Math.floor((pos - 1) / 2);
if (compare(data[pos], data[parent]) < 0) {
swap(data, parent, pos);
pos = parent;
} else break;
}
},
_down: function (pos) {
var data = this.data,
compare = this.compare,
len = this.length;
while (true) {
var left = 2 * pos + 1,
right = left + 1,
min = pos;
if (left < len && compare(data[left], data[min]) < 0) min = left;
if (right < len && compare(data[right], data[min]) < 0) min = right;
if (min === pos) return;
swap(data, min, pos);
pos = min;
}
}
};
function swap(data, i, j) {
var tmp = data[i];
data[i] = data[j];
data[j] = tmp;
}
},{}]},{},[1])(1)
});

387
libs/priority-queue.js Normal file
View file

@ -0,0 +1,387 @@
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.PriorityQueue = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
var AbstractPriorityQueue, ArrayStrategy, BHeapStrategy, BinaryHeapStrategy, PriorityQueue,
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
AbstractPriorityQueue = _dereq_('./PriorityQueue/AbstractPriorityQueue');
ArrayStrategy = _dereq_('./PriorityQueue/ArrayStrategy');
BinaryHeapStrategy = _dereq_('./PriorityQueue/BinaryHeapStrategy');
BHeapStrategy = _dereq_('./PriorityQueue/BHeapStrategy');
PriorityQueue = (function(superClass) {
extend(PriorityQueue, superClass);
function PriorityQueue(options) {
options || (options = {});
options.strategy || (options.strategy = BinaryHeapStrategy);
options.comparator || (options.comparator = function(a, b) {
return (a || 0) - (b || 0);
});
PriorityQueue.__super__.constructor.call(this, options);
}
return PriorityQueue;
})(AbstractPriorityQueue);
PriorityQueue.ArrayStrategy = ArrayStrategy;
PriorityQueue.BinaryHeapStrategy = BinaryHeapStrategy;
PriorityQueue.BHeapStrategy = BHeapStrategy;
module.exports = PriorityQueue;
},{"./PriorityQueue/AbstractPriorityQueue":2,"./PriorityQueue/ArrayStrategy":3,"./PriorityQueue/BHeapStrategy":4,"./PriorityQueue/BinaryHeapStrategy":5}],2:[function(_dereq_,module,exports){
var AbstractPriorityQueue;
module.exports = AbstractPriorityQueue = (function() {
function AbstractPriorityQueue(options) {
var ref;
if ((options != null ? options.strategy : void 0) == null) {
throw 'Must pass options.strategy, a strategy';
}
if ((options != null ? options.comparator : void 0) == null) {
throw 'Must pass options.comparator, a comparator';
}
this.priv = new options.strategy(options);
this.length = (options != null ? (ref = options.initialValues) != null ? ref.length : void 0 : void 0) || 0;
}
AbstractPriorityQueue.prototype.queue = function(value) {
this.length++;
this.priv.queue(value);
return void 0;
};
AbstractPriorityQueue.prototype.dequeue = function(value) {
if (!this.length) {
throw 'Empty queue';
}
this.length--;
return this.priv.dequeue();
};
AbstractPriorityQueue.prototype.peek = function(value) {
if (!this.length) {
throw 'Empty queue';
}
return this.priv.peek();
};
AbstractPriorityQueue.prototype.clear = function() {
this.length = 0;
return this.priv.clear();
};
return AbstractPriorityQueue;
})();
},{}],3:[function(_dereq_,module,exports){
var ArrayStrategy, binarySearchForIndexReversed;
binarySearchForIndexReversed = function(array, value, comparator) {
var high, low, mid;
low = 0;
high = array.length;
while (low < high) {
mid = (low + high) >>> 1;
if (comparator(array[mid], value) >= 0) {
low = mid + 1;
} else {
high = mid;
}
}
return low;
};
module.exports = ArrayStrategy = (function() {
function ArrayStrategy(options) {
var ref;
this.options = options;
this.comparator = this.options.comparator;
this.data = ((ref = this.options.initialValues) != null ? ref.slice(0) : void 0) || [];
this.data.sort(this.comparator).reverse();
}
ArrayStrategy.prototype.queue = function(value) {
var pos;
pos = binarySearchForIndexReversed(this.data, value, this.comparator);
this.data.splice(pos, 0, value);
return void 0;
};
ArrayStrategy.prototype.dequeue = function() {
return this.data.pop();
};
ArrayStrategy.prototype.peek = function() {
return this.data[this.data.length - 1];
};
ArrayStrategy.prototype.clear = function() {
this.data.length = 0;
return void 0;
};
return ArrayStrategy;
})();
},{}],4:[function(_dereq_,module,exports){
var BHeapStrategy;
module.exports = BHeapStrategy = (function() {
function BHeapStrategy(options) {
var arr, i, j, k, len, ref, ref1, shift, value;
this.comparator = (options != null ? options.comparator : void 0) || function(a, b) {
return a - b;
};
this.pageSize = (options != null ? options.pageSize : void 0) || 512;
this.length = 0;
shift = 0;
while ((1 << shift) < this.pageSize) {
shift += 1;
}
if (1 << shift !== this.pageSize) {
throw 'pageSize must be a power of two';
}
this._shift = shift;
this._emptyMemoryPageTemplate = arr = [];
for (i = j = 0, ref = this.pageSize; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) {
arr.push(null);
}
this._memory = [];
this._mask = this.pageSize - 1;
if (options.initialValues) {
ref1 = options.initialValues;
for (k = 0, len = ref1.length; k < len; k++) {
value = ref1[k];
this.queue(value);
}
}
}
BHeapStrategy.prototype.queue = function(value) {
this.length += 1;
this._write(this.length, value);
this._bubbleUp(this.length, value);
return void 0;
};
BHeapStrategy.prototype.dequeue = function() {
var ret, val;
ret = this._read(1);
val = this._read(this.length);
this.length -= 1;
if (this.length > 0) {
this._write(1, val);
this._bubbleDown(1, val);
}
return ret;
};
BHeapStrategy.prototype.peek = function() {
return this._read(1);
};
BHeapStrategy.prototype.clear = function() {
this.length = 0;
this._memory.length = 0;
return void 0;
};
BHeapStrategy.prototype._write = function(index, value) {
var page;
page = index >> this._shift;
while (page >= this._memory.length) {
this._memory.push(this._emptyMemoryPageTemplate.slice(0));
}
return this._memory[page][index & this._mask] = value;
};
BHeapStrategy.prototype._read = function(index) {
return this._memory[index >> this._shift][index & this._mask];
};
BHeapStrategy.prototype._bubbleUp = function(index, value) {
var compare, indexInPage, parentIndex, parentValue;
compare = this.comparator;
while (index > 1) {
indexInPage = index & this._mask;
if (index < this.pageSize || indexInPage > 3) {
parentIndex = (index & ~this._mask) | (indexInPage >> 1);
} else if (indexInPage < 2) {
parentIndex = (index - this.pageSize) >> this._shift;
parentIndex += parentIndex & ~(this._mask >> 1);
parentIndex |= this.pageSize >> 1;
} else {
parentIndex = index - 2;
}
parentValue = this._read(parentIndex);
if (compare(parentValue, value) < 0) {
break;
}
this._write(parentIndex, value);
this._write(index, parentValue);
index = parentIndex;
}
return void 0;
};
BHeapStrategy.prototype._bubbleDown = function(index, value) {
var childIndex1, childIndex2, childValue1, childValue2, compare;
compare = this.comparator;
while (index < this.length) {
if (index > this._mask && !(index & (this._mask - 1))) {
childIndex1 = childIndex2 = index + 2;
} else if (index & (this.pageSize >> 1)) {
childIndex1 = (index & ~this._mask) >> 1;
childIndex1 |= index & (this._mask >> 1);
childIndex1 = (childIndex1 + 1) << this._shift;
childIndex2 = childIndex1 + 1;
} else {
childIndex1 = index + (index & this._mask);
childIndex2 = childIndex1 + 1;
}
if (childIndex1 !== childIndex2 && childIndex2 <= this.length) {
childValue1 = this._read(childIndex1);
childValue2 = this._read(childIndex2);
if (compare(childValue1, value) < 0 && compare(childValue1, childValue2) <= 0) {
this._write(childIndex1, value);
this._write(index, childValue1);
index = childIndex1;
} else if (compare(childValue2, value) < 0) {
this._write(childIndex2, value);
this._write(index, childValue2);
index = childIndex2;
} else {
break;
}
} else if (childIndex1 <= this.length) {
childValue1 = this._read(childIndex1);
if (compare(childValue1, value) < 0) {
this._write(childIndex1, value);
this._write(index, childValue1);
index = childIndex1;
} else {
break;
}
} else {
break;
}
}
return void 0;
};
return BHeapStrategy;
})();
},{}],5:[function(_dereq_,module,exports){
var BinaryHeapStrategy;
module.exports = BinaryHeapStrategy = (function() {
function BinaryHeapStrategy(options) {
var ref;
this.comparator = (options != null ? options.comparator : void 0) || function(a, b) {
return a - b;
};
this.length = 0;
this.data = ((ref = options.initialValues) != null ? ref.slice(0) : void 0) || [];
this._heapify();
}
BinaryHeapStrategy.prototype._heapify = function() {
var i, j, ref;
if (this.data.length > 0) {
for (i = j = 1, ref = this.data.length; 1 <= ref ? j < ref : j > ref; i = 1 <= ref ? ++j : --j) {
this._bubbleUp(i);
}
}
return void 0;
};
BinaryHeapStrategy.prototype.queue = function(value) {
this.data.push(value);
this._bubbleUp(this.data.length - 1);
return void 0;
};
BinaryHeapStrategy.prototype.dequeue = function() {
var last, ret;
ret = this.data[0];
last = this.data.pop();
if (this.data.length > 0) {
this.data[0] = last;
this._bubbleDown(0);
}
return ret;
};
BinaryHeapStrategy.prototype.peek = function() {
return this.data[0];
};
BinaryHeapStrategy.prototype.clear = function() {
this.length = 0;
this.data.length = 0;
return void 0;
};
BinaryHeapStrategy.prototype._bubbleUp = function(pos) {
var parent, x;
while (pos > 0) {
parent = (pos - 1) >>> 1;
if (this.comparator(this.data[pos], this.data[parent]) < 0) {
x = this.data[parent];
this.data[parent] = this.data[pos];
this.data[pos] = x;
pos = parent;
} else {
break;
}
}
return void 0;
};
BinaryHeapStrategy.prototype._bubbleDown = function(pos) {
var last, left, minIndex, right, x;
last = this.data.length - 1;
while (true) {
left = (pos << 1) + 1;
right = left + 1;
minIndex = pos;
if (left <= last && this.comparator(this.data[left], this.data[minIndex]) < 0) {
minIndex = left;
}
if (right <= last && this.comparator(this.data[right], this.data[minIndex]) < 0) {
minIndex = right;
}
if (minIndex !== pos) {
x = this.data[minIndex];
this.data[minIndex] = this.data[pos];
this.data[pos] = x;
pos = minIndex;
} else {
break;
}
}
return void 0;
};
return BinaryHeapStrategy;
})();
},{}]},{},[1])(1)
});

436
libs/quantize.js Normal file
View file

@ -0,0 +1,436 @@
// Forked from color-thief.js Copyright 2011 Lokesh Dhakar under MIT license
// var pixelArray = [[190,197,190], [202,204,200], [207,214,210]]; // ... etc;
// var cmap = MMCQ.quantize(pixelArray, colorCount);
// var palette = cmap ? cmap.palette() : null;
// Protovis. Copyright 2010 Stanford Visualization Group (http://mbostock.github.com/protovis/)
// Licensed under the BSD License: http://www.opensource.org/licenses/bsd-license.php
if (!pv) {
var pv = {
map: function(array, f) {
var o = {};
return f ? array.map(function(d, i) { o.index = i; return f.call(o, d); }) : array.slice();
},
naturalOrder: function(a, b) {
return (a < b) ? -1 : ((a > b) ? 1 : 0);
},
sum: function(array, f) {
var o = {};
return array.reduce(f ? function(p, d, i) { o.index = i; return p + f.call(o, d); } : function(p, d) { return p + d; }, 0);
},
max: function(array, f) {
return Math.max.apply(null, f ? pv.map(array, f) : array);
}
};
}
// MMCQ (Modified median cut quantization). Algorithm from the Leptonica library, modified by Nick Rabinowitz
// quantize.js Copyright 2008 Nick Rabinowitz under MIT license
var MMCQ = (function() {
// private constants
var sigbits = 5,
rshift = 8 - sigbits,
maxIterations = 1000,
fractByPopulations = 0.75;
// get reduced-space color index for a pixel
function getColorIndex(r, g, b) {
return (r << (2 * sigbits)) + (g << sigbits) + b;
}
// Simple priority queue
function PQueue(comparator) {
var contents = [],
sorted = false;
function sort() {
contents.sort(comparator);
sorted = true;
}
return {
push: function(o) {
contents.push(o);
sorted = false;
},
peek: function(index) {
if (!sorted) sort();
if (index===undefined) index = contents.length - 1;
return contents[index];
},
pop: function() {
if (!sorted) sort();
return contents.pop();
},
size: function() {
return contents.length;
},
map: function(f) {
return contents.map(f);
},
debug: function() {
if (!sorted) sort();
return contents;
}
};
}
// 3d color space box
function VBox(r1, r2, g1, g2, b1, b2, histo) {
var vbox = this;
vbox.r1 = r1;
vbox.r2 = r2;
vbox.g1 = g1;
vbox.g2 = g2;
vbox.b1 = b1;
vbox.b2 = b2;
vbox.histo = histo;
}
VBox.prototype = {
volume: function(force) {
var vbox = this;
if (!vbox._volume || force) {
vbox._volume = ((vbox.r2 - vbox.r1 + 1) * (vbox.g2 - vbox.g1 + 1) * (vbox.b2 - vbox.b1 + 1));
}
return vbox._volume;
},
count: function(force) {
var vbox = this,
histo = vbox.histo;
if (!vbox._count_set || force) {
var npix = 0,
index, i, j, k;
for (i = vbox.r1; i <= vbox.r2; i++) {
for (j = vbox.g1; j <= vbox.g2; j++) {
for (k = vbox.b1; k <= vbox.b2; k++) {
index = getColorIndex(i,j,k);
npix += (histo[index] || 0);
}
}
}
vbox._count = npix;
vbox._count_set = true;
}
return vbox._count;
},
copy: function() {
var vbox = this;
return new VBox(vbox.r1, vbox.r2, vbox.g1, vbox.g2, vbox.b1, vbox.b2, vbox.histo);
},
avg: function(force) {
var vbox = this,
histo = vbox.histo;
if (!vbox._avg || force) {
var ntot = 0,
mult = 1 << (8 - sigbits),
rsum = 0,
gsum = 0,
bsum = 0,
hval,
i, j, k, histoindex;
for (i = vbox.r1; i <= vbox.r2; i++) {
for (j = vbox.g1; j <= vbox.g2; j++) {
for (k = vbox.b1; k <= vbox.b2; k++) {
histoindex = getColorIndex(i,j,k);
hval = histo[histoindex] || 0;
ntot += hval;
rsum += (hval * (i + 0.5) * mult);
gsum += (hval * (j + 0.5) * mult);
bsum += (hval * (k + 0.5) * mult);
}
}
}
if (ntot) {
vbox._avg = [~~(rsum/ntot), ~~(gsum/ntot), ~~(bsum/ntot)];
} else {
// console.log('empty box');
vbox._avg = [
~~(mult * (vbox.r1 + vbox.r2 + 1) / 2),
~~(mult * (vbox.g1 + vbox.g2 + 1) / 2),
~~(mult * (vbox.b1 + vbox.b2 + 1) / 2)
];
}
}
return vbox._avg;
},
contains: function(pixel) {
var vbox = this,
rval = pixel[0] >> rshift;
gval = pixel[1] >> rshift;
bval = pixel[2] >> rshift;
return (rval >= vbox.r1 && rval <= vbox.r2 &&
gval >= vbox.g1 && gval <= vbox.g2 &&
bval >= vbox.b1 && bval <= vbox.b2);
}
};
// Color map
function CMap() {
this.vboxes = new PQueue(function(a,b) {
return pv.naturalOrder(
a.vbox.count()*a.vbox.volume(),
b.vbox.count()*b.vbox.volume()
);
});
}
CMap.prototype = {
push: function(vbox) {
this.vboxes.push({
vbox: vbox,
color: vbox.avg()
});
},
palette: function() {
return this.vboxes.map(function(vb) { return vb.color; });
},
size: function() {
return this.vboxes.size();
},
map: function(color) {
var vboxes = this.vboxes;
for (var i=0; i<vboxes.size(); i++) {
if (vboxes.peek(i).vbox.contains(color)) {
return vboxes.peek(i).color;
}
}
return this.nearest(color);
},
nearest: function(color) {
var vboxes = this.vboxes,
d1, d2, pColor;
for (var i=0; i<vboxes.size(); i++) {
d2 = Math.sqrt(
Math.pow(color[0] - vboxes.peek(i).color[0], 2) +
Math.pow(color[1] - vboxes.peek(i).color[1], 2) +
Math.pow(color[2] - vboxes.peek(i).color[2], 2)
);
if (d2 < d1 || d1 === undefined) {
d1 = d2;
pColor = vboxes.peek(i).color;
}
}
return pColor;
},
forcebw: function() {
// XXX: won't work yet
var vboxes = this.vboxes;
vboxes.sort(function(a,b) { return pv.naturalOrder(pv.sum(a.color), pv.sum(b.color));});
// force darkest color to black if everything < 5
var lowest = vboxes[0].color;
if (lowest[0] < 5 && lowest[1] < 5 && lowest[2] < 5)
vboxes[0].color = [0,0,0];
// force lightest color to white if everything > 251
var idx = vboxes.length-1,
highest = vboxes[idx].color;
if (highest[0] > 251 && highest[1] > 251 && highest[2] > 251)
vboxes[idx].color = [255,255,255];
}
};
// histo (1-d array, giving the number of pixels in
// each quantized region of color space), or null on error
function getHisto(pixels) {
var histosize = 1 << (3 * sigbits),
histo = new Array(histosize),
index, rval, gval, bval;
pixels.forEach(function(pixel) {
rval = pixel[0] >> rshift;
gval = pixel[1] >> rshift;
bval = pixel[2] >> rshift;
index = getColorIndex(rval, gval, bval);
histo[index] = (histo[index] || 0) + 1;
});
return histo;
}
function vboxFromPixels(pixels, histo) {
var rmin=1000000, rmax=0,
gmin=1000000, gmax=0,
bmin=1000000, bmax=0,
rval, gval, bval;
// find min/max
pixels.forEach(function(pixel) {
rval = pixel[0] >> rshift;
gval = pixel[1] >> rshift;
bval = pixel[2] >> rshift;
if (rval < rmin) rmin = rval;
else if (rval > rmax) rmax = rval;
if (gval < gmin) gmin = gval;
else if (gval > gmax) gmax = gval;
if (bval < bmin) bmin = bval;
else if (bval > bmax) bmax = bval;
});
return new VBox(rmin, rmax, gmin, gmax, bmin, bmax, histo);
}
function medianCutApply(histo, vbox) {
if (!vbox.count()) return;
var rw = vbox.r2 - vbox.r1 + 1,
gw = vbox.g2 - vbox.g1 + 1,
bw = vbox.b2 - vbox.b1 + 1,
maxw = pv.max([rw, gw, bw]);
// only one pixel, no split
if (vbox.count() == 1) {
return [vbox.copy()];
}
/* Find the partial sum arrays along the selected axis. */
var total = 0,
partialsum = [],
lookaheadsum = [],
i, j, k, sum, index;
if (maxw == rw) {
for (i = vbox.r1; i <= vbox.r2; i++) {
sum = 0;
for (j = vbox.g1; j <= vbox.g2; j++) {
for (k = vbox.b1; k <= vbox.b2; k++) {
index = getColorIndex(i,j,k);
sum += (histo[index] || 0);
}
}
total += sum;
partialsum[i] = total;
}
}
else if (maxw == gw) {
for (i = vbox.g1; i <= vbox.g2; i++) {
sum = 0;
for (j = vbox.r1; j <= vbox.r2; j++) {
for (k = vbox.b1; k <= vbox.b2; k++) {
index = getColorIndex(j,i,k);
sum += (histo[index] || 0);
}
}
total += sum;
partialsum[i] = total;
}
}
else { /* maxw == bw */
for (i = vbox.b1; i <= vbox.b2; i++) {
sum = 0;
for (j = vbox.r1; j <= vbox.r2; j++) {
for (k = vbox.g1; k <= vbox.g2; k++) {
index = getColorIndex(j,k,i);
sum += (histo[index] || 0);
}
}
total += sum;
partialsum[i] = total;
}
}
partialsum.forEach(function(d,i) {
lookaheadsum[i] = total-d;
});
function doCut(color) {
var dim1 = color + '1',
dim2 = color + '2',
left, right, vbox1, vbox2, d2, count2=0;
for (i = vbox[dim1]; i <= vbox[dim2]; i++) {
if (partialsum[i] > total / 2) {
vbox1 = vbox.copy();
vbox2 = vbox.copy();
left = i - vbox[dim1];
right = vbox[dim2] - i;
if (left <= right)
d2 = Math.min(vbox[dim2] - 1, ~~(i + right / 2));
else d2 = Math.max(vbox[dim1], ~~(i - 1 - left / 2));
// avoid 0-count boxes
while (!partialsum[d2]) d2++;
count2 = lookaheadsum[d2];
while (!count2 && partialsum[d2-1]) count2 = lookaheadsum[--d2];
// set dimensions
vbox1[dim2] = d2;
vbox2[dim1] = vbox1[dim2] + 1;
// console.log('vbox counts:', vbox.count(), vbox1.count(), vbox2.count());
return [vbox1, vbox2];
}
}
}
// determine the cut planes
return maxw == rw ? doCut('r') :
maxw == gw ? doCut('g') :
doCut('b');
}
function quantize(pixels, maxcolors) {
maxcolors++;
if (!pixels.length || maxcolors < 2 || maxcolors > 256) {return false;}
// XXX: check color content and convert to grayscale if insufficient
var histo = getHisto(pixels),
histosize = 1 << (3 * sigbits);
// check that we aren't below maxcolors already
var nColors = 0;
histo.forEach(function() { nColors++; });
if (nColors <= maxcolors) {
// XXX: generate the new colors from the histo and return
}
// get the beginning vbox from the colors
var vbox = vboxFromPixels(pixels, histo),
pq = new PQueue(function(a,b) { return pv.naturalOrder(a.count(), b.count()); });
pq.push(vbox);
// inner function to do the iteration
function iter(lh, target) {
var ncolors = 1,
niters = 0,
vbox;
while (niters < maxIterations) {
vbox = lh.pop();
if (!vbox.count()) { /* just put it back */
lh.push(vbox);
niters++;
continue;
}
// do the cut
var vboxes = medianCutApply(histo, vbox),
vbox1 = vboxes[0],
vbox2 = vboxes[1];
if (!vbox1) {
// console.log("vbox1 not defined; shouldn't happen!");
return;
}
lh.push(vbox1);
if (vbox2) { /* vbox2 can be null */
lh.push(vbox2);
ncolors++;
}
if (ncolors >= target) return;
if (niters++ > maxIterations) {
// console.log("infinite loop; perhaps too few pixels!");
return;
}
}
}
// first set of colors, sorted by population
iter(pq, fractByPopulations * maxcolors);
// Re-sort by the product of pixel occupancy times the size in color space.
var pq2 = new PQueue(function(a,b) {
return pv.naturalOrder(a.count()*a.volume(), b.count()*b.volume());
});
while (pq.size()) {
pq2.push(pq.pop());
}
// next set - generate the median cuts using the (npix * vol) sorting.
iter(pq2, maxcolors - pq2.size());
// calculate the actual colors
var cmap = new CMap();
while (pq2.size()) {cmap.push(pq2.pop());}
return cmap;
}
return {
quantize: quantize
};
})();