vprsdf
Version:
Tools for creating signed distance fields from image data
162 lines (161 loc) • 5.46 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.sdf = void 0;
function sdf(inFilled, width) {
var height = Math.floor(inFilled.length / width);
var halfVector = new Array(inFilled.length).fill([
2 * width + 1,
2 * height + 1,
]);
sdfPartial(inFilled, width, halfVector, euclidean, false);
sdfPartial(inFilled, width, halfVector, euclidean, true);
var out = new Array(inFilled.length);
for (var i = 0; i < halfVector.length; i++) {
var _a = halfVector[i], dx = _a[0], dy = _a[1];
out[i] = euclidean(dx, dy) / 2;
if (inFilled[i]) {
out[i] = -out[i];
}
}
return out;
}
exports.sdf = sdf;
function euclidean(dx, dy) {
return Math.sqrt(dx * dx + dy * dy);
}
function sdfPartial(inFilled, width, inHalfVector, metric, negate) {
if (width === 0)
throw new Error("Width cannot be zero.");
var height = Math.floor(inFilled.length / width);
if (height === 0)
throw new Error("Height cannot be zero.");
var validPixel = function (x, y) {
return x >= 0 && x < width && y >= 0 && y < height;
};
var coord = function (x, y) { return x + width * y; };
var filled = function (x, y) {
return validPixel(x, y) ? inFilled[coord(x, y)] !== negate : negate;
};
var doNeighbors = function (x, y, f) {
for (var dy = -1; dy <= 1; dy++) {
for (var dx = -1; dx <= 1; dx++) {
if (validPixel(x + dx, y + dy)) {
f(x + dx, y + dy);
}
}
}
};
var halfVector = inHalfVector;
var closed = new Array(inFilled.length).fill(false);
var QueueCompare = /** @class */ (function () {
function QueueCompare() {
}
QueueCompare.prototype.compare = function (a, b) {
return a.dist > b.dist;
};
return QueueCompare;
}());
var pq = new PriorityQueue(new QueueCompare().compare);
var addToQueue = function (x, y, dx, dy) {
pq.push({ x: x, y: y, dx: dx, dy: dy, dist: metric(dx, dy) });
};
var _loop_1 = function (y) {
var _loop_3 = function (x) {
if (filled(x, y)) {
doNeighbors(x, y, function (x2, y2) {
if (!filled(x2, y2)) {
addToQueue(x2, y2, x2 - x, y2 - y);
}
});
}
};
for (var x = 0; x < width; x++) {
_loop_3(x);
}
};
for (var y = 0; y < height; y++) {
_loop_1(y);
}
var _loop_2 = function () {
var current = pq.pop();
if (closed[coord(current.x, current.y)])
return "continue";
closed[coord(current.x, current.y)] = true;
halfVector[coord(current.x, current.y)] = [current.dx, current.dy];
doNeighbors(current.x, current.y, function (x2, y2) {
if (!filled(x2, y2) && !closed[coord(x2, y2)]) {
var dx = 2 * (x2 - current.x);
var dy = 2 * (y2 - current.y);
var _a = halfVector[coord(current.x, current.y)], ddx = _a[0], ddy = _a[1];
dx += ddx;
dy += ddy;
addToQueue(x2, y2, dx, dy);
}
});
};
while (!pq.isEmpty()) {
_loop_2();
}
}
// PriorityQueue implementation
var PriorityQueue = /** @class */ (function () {
function PriorityQueue(compare) {
this.heap = [];
this.compare = compare;
}
PriorityQueue.prototype.push = function (item) {
this.heap.push(item);
this.bubbleUp(this.heap.length - 1);
};
PriorityQueue.prototype.pop = function () {
var top = this.heap[0];
var bottom = this.heap.pop();
if (this.heap.length > 0 && bottom !== undefined) {
this.heap[0] = bottom;
this.sinkDown(0);
}
return top;
};
PriorityQueue.prototype.isEmpty = function () {
return this.heap.length === 0;
};
PriorityQueue.prototype.bubbleUp = function (index) {
var element = this.heap[index];
while (index > 0) {
var parentIndex = Math.floor((index + 1) / 2) - 1;
var parent_1 = this.heap[parentIndex];
if (this.compare(element, parent_1))
break;
this.heap[index] = parent_1;
index = parentIndex;
}
this.heap[index] = element;
};
PriorityQueue.prototype.sinkDown = function (index) {
var length = this.heap.length;
var element = this.heap[index];
while (true) {
var swap = null;
var leftIndex = 2 * (index + 1) - 1;
var rightIndex = 2 * (index + 1);
if (leftIndex < length) {
var left = this.heap[leftIndex];
if (!this.compare(left, element)) {
swap = leftIndex;
}
}
if (rightIndex < length) {
var right = this.heap[rightIndex];
if (!this.compare(right, swap === null ? element : this.heap[leftIndex])) {
swap = rightIndex;
}
}
if (swap === null)
break;
this.heap[index] = this.heap[swap];
index = swap;
}
this.heap[index] = element;
};
return PriorityQueue;
}());