159 lines
5.4 KiB
TypeScript
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;
|
|
}
|
|
}
|
|
}
|
|
}
|