pathanimator/inbrowser/ts/algorithm/DistancePath.ts

159 lines
5.4 KiB
TypeScript

/// <reference path="Algorithm.ts" />
/**
* Distance Path algorithm implementation
*/
class DistancePath extends Algorithm {
/**
* Pixels updated by last iteration. Value = x<<16 + y
*/
private last: number[];
/**
* Pixels updated by current iteration. Value = x<<16 + y
*/
private next: number[];
/**
* Weight of a block (Pixel with value 0)
*/
private weightBlock: number;
/**
* Weight of a path (Pixel with value 255)
*/
private weightPath: number;
/**
* Treshold value when a pixel becomes a path (0...255)
*/
private treshold: number;
/**
* Maximum distance before path becomes no longer traversable
*/
private maxDistance: number;
/**
* Input pixel data (RGBA)
*/
private inRAW: Uint8ClampedArray;
/**
* Temporary distance map
*/
private outRAW: number[];
/**
* Constructor
* @param config Parameters from UI
*/
constructor(config: Object) {
super();
this.last = [];
this.next = [];
this.weightBlock = parseFloat(config['weightblock']);
this.weightPath = parseFloat(config['weightpath']);
this.treshold = parseInt(config['treshold'], 10);
this.maxDistance = parseFloat(config['maxdistance']);
// start pixel
var start = config['startpoints'].split(" ");
for (var i = 0; i < start.length; ++i) {
var pos = start[i].split(",");
this.last.push((parseInt(pos[0]) << 16) + parseInt(pos[1]));
}
}
/**
* Process a single pixel.
* @param x X-coordinate
* @param y Y-coordinate
* @param lastValue Last value of the pixel that triggered (re)calculation
* @param width image width
*/
processPixel(x: number, y: number, lastValue: number, width: number) {
var offset = (y * width + x);
var inOffset = offset * 4;
var avg = (this.inRAW[inOffset] + this.inRAW[inOffset + 1] + this.inRAW[inOffset + 2]) / 765;
var newValue = lastValue + (1 - avg) * (this.weightBlock - this.weightPath) + this.weightPath;
if (newValue > this.maxDistance) newValue = this.maxDistance;
if (newValue < this.outRAW[offset]) {
this.next.push((x << 16) + y);
this.outRAW[offset] = newValue;
}
}
/**
* Run the algorithm
*/
run() {
var width = this.input.getWidth();
var height = this.input.getHeight();
this.inRAW = this.input.getPixels8();
var end = width * height;
// allocate output storage and set to m_maxDistance
this.outRAW = new Array<number>(width * height);
for (var i = 0; i < end; i++) this.outRAW[i] = this.maxDistance;
// set start pixels to distance of 0
this.last.forEach((coordinate) => {
var x = coordinate >> 16;
var y = coordinate & 0xFFFF;
this.outRAW[(width * y + x)] = 0;
});
var outPixels = this.output.getPixels8();
var iteration = 0;
do {
// process pixel from last time
for (var i = 0; i < this.last.length; ++i) {
var coordinate = this.last[i];
var x = coordinate >> 16;
var y = coordinate & 0xFFFF;
var lastValue = this.outRAW[y * width + x];
if (y > 0) { // process row on top
if (x > 0) this.processPixel(x - 1, y - 1, lastValue, width);
this.processPixel(x, y - 1, lastValue, width);
if (x < (width - 1)) this.processPixel(x + 1, y - 1, lastValue, width);
}
if (x > 0) this.processPixel(x - 1, y, lastValue, width);
if (x < (width - 1)) this.processPixel(x + 1, y, lastValue, width);
if (y < (height - 1)) { // process row on bottom
if (x > 0) this.processPixel(x - 1, y + 1, lastValue, width);
this.processPixel(x, y + 1, lastValue, width);
if (x < (width - 1)) this.processPixel(x + 1, y + 1, lastValue, width);
}
}
++iteration;
// clear out old list
this.last = this.next;
this.next = [];
if (iteration % 1000 == 0) {
this.callback.messageCallback("iteration: " + iteration, false);
}
if (iteration > 10000) break;
} while (this.last.length > 0);
// find max value
var max = 0;
var threshold = this.treshold * 3; // multiply by 3 here saves many division from sum to average in the loops below
// could be optimized, but it is only run once for every pixel
for (var i = 0; i < end; ++i) {
var offset = i * 4;
var sum = (this.inRAW[offset] + this.inRAW[offset + 1] + this.inRAW[offset + 2]);
// only update max if it is larger than previous, but still smaller than our max distance and larger as the threshhold (visible path cheat)
if (this.outRAW[i] > max && this.outRAW[i] < this.maxDistance && (sum > threshold)) max = this.outRAW[i];
}
this.callback.messageCallback("took " + iteration + " iterations max value: " + max, false);
max = 65535.0 / max; // results in 0 ... 65535 range
// write result to output
var inOffset = 0;
var outOffset = 0;
for (var i = 0; i < end; ++i) {
var sum = (this.inRAW[inOffset++] + this.inRAW[inOffset++] + this.inRAW[inOffset++]);
++inOffset;
if (sum > threshold) {
var value = this.outRAW[i] * max;
outPixels[outOffset++] = value >> 8; // red = distance high byte
outPixels[outOffset++] = value & 0xFF; // green = distance low byte
outPixels[outOffset++] = 255; // blue= a pathable pixel
} else {
outPixels[outOffset++] = 0;
outPixels[outOffset++] = 0;
outPixels[outOffset++] = 0;
}
}
}
}