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