pathanimator/inbrowser/webroot/index.html

462 lines
18 KiB
HTML

<html>
<head>
<title>Path Animator Tool</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
body{background:#000;color:f9f9f9;padding:10px}#algorithm label{width:250px;display:inline-block}.hidden{display:none !important}.running:before{display:inline-block;content:' ';border-radius:50%;border:.5rem solid rgba(0,0,0,0.25);border-top-color:#fff;animation:spin 1s infinite linear;width:16px;height:16px}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}#drop-area{border:2px dashed #ccc;border-radius:20px;max-width:480px;width:50%;font-family:sans-serif;margin:10px 0;padding:20px}#drop-area.highlight{border-color:purple}.button{display:inline-block;padding:10px;color:#000;background:#ccc;cursor:pointer;border-radius:5px;border:1px solid #ccc}.button:hover{background:#ddd}#fileElem{display:none}
</style>
</head>
<body>
<h2>1. Source image</h2>
<div id="drop-area">
<form>
<p>Select image with the file dialog or by dragging and dropping an image onto the dashed region.</p>
<input type="file" id="fileElem" accept="image/*" />
<label class="button" for="fileElem">Select an image</label>
</form>
<div id="gallery"></div>
</div>
<h2>2. Configure algorithm</h2>
<div id="algorithm">
<form>
<div><label for="algorithm">Select the algorithm to use:</label>
<select name="algorithm" id="alsel">
<option value="distancepath" selected="selected">Distance Path</option>
<option value="noise">Noise</option>
</select>
</div>
<div id="distancepath">
<h3>Distance path</h3>
<div><label for="weightpath">Weight of path</label> <input type="text" name="weightpath" value="1"/></div>
<div><label for="weightblock">Weight of block</label> <input type="text" name="weightblock" value="1000"/></div>
<div><label for="threshold">Treshold for traversable pixel</label> <input type="text" name="treshold" value="210"/></div>
<div><label for="maxdistance">Maximum distance value</label> <input type="text" name="maxdistance" value="3000000"/></div>
<div><label for="startpoints">List of starting points Format:<br />x1,y1 x2,y2 ...</label> <input type="text" name="startpoints" value="0,0"/></div>
</div>
<div id="noise" class="hidden">
<h3>Noise</h3>
Nothing to configure.
</div>
<p>
<button id="run" class="button hidden">run</button>
</p>
</form>
</div>
<h2>3. Output image</h2>
<div id="output">
</div>
<p><button id="save" class="button hidden">Save image</button></p>
<canvas id="outputcanvas" class="hidden"></canvas>
<canvas id="inputcanvas" class="hidden"></canvas>
<script type="text/javascript">
var Algorithm = (function () {
function Algorithm() {
this.callback = null;
this.output = null;
}
Algorithm.prototype.setCallback = function (callback) {
this.callback = callback;
};
Algorithm.prototype.setInput = function (image) {
this.input = image;
};
Algorithm.prototype.setOutput = function (image) {
this.output = image;
};
Algorithm.prototype.run = function () {
};
return Algorithm;
}());
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var DistancePath = (function (_super) {
__extends(DistancePath, _super);
function DistancePath(config) {
var _this = _super.call(this) || this;
_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']);
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]));
}
return _this;
}
DistancePath.prototype.processPixel = function (x, y, lastValue, width) {
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;
}
};
DistancePath.prototype.run = function () {
var _this = this;
var width = this.input.getWidth();
var height = this.input.getHeight();
this.inRAW = this.input.getPixels8();
var end = width * height;
this.outRAW = new Array(width * height);
for (var i = 0; i < end; i++)
this.outRAW[i] = this.maxDistance;
this.last.forEach(function (coordinate) {
var x = coordinate >> 16;
var y = coordinate & 0xFFFF;
_this.outRAW[(width * y + x)] = 0;
});
var outPixels = this.output.getPixels8();
var iteration = 0;
do {
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) {
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)) {
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;
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);
var max = 0;
var threshold = this.treshold * 3;
for (var i = 0; i < end; ++i) {
var offset = i * 4;
var sum = (this.inRAW[offset] + this.inRAW[offset + 1] + this.inRAW[offset + 2]);
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;
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;
outPixels[outOffset++] = value & 0xFF;
outPixels[outOffset++] = 255;
}
else {
outPixels[outOffset++] = 0;
outPixels[outOffset++] = 0;
outPixels[outOffset++] = 0;
}
}
};
return DistancePath;
}(Algorithm));
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var Noise = (function (_super) {
__extends(Noise, _super);
function Noise() {
return _super !== null && _super.apply(this, arguments) || this;
}
Noise.prototype.run = function () {
var width = this.output.getWidth();
var height = this.output.getHeight();
var pixels = this.output.getPixels8();
var end = width * height * 3;
for (var i = 0; i < end; ++i) {
pixels[i] = Math.random() * 255;
}
};
return Noise;
}(Algorithm));
var BrowserHandler = (function () {
function BrowserHandler(instance) {
var _this = this;
this.instance = instance;
this.isHighlighted = false;
this.dropArea = document.getElementById('drop-area');
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(function (eventName) {
_this.dropArea.addEventListener(eventName, _this.preventDefaults, false);
});
this.dropArea.addEventListener('dragover', this.dragOver.bind(this), false);
this.dropArea.addEventListener('drop', this.dragDrop.bind(this), false);
this.algorithmElem = document.getElementById("alsel");
this.algorithmElem.addEventListener("change", this.algorithmChanged.bind(this));
this.algorithmChanged();
document.getElementById("fileElem").addEventListener("change", this.handleSourceFile.bind(this));
document.getElementById("run").addEventListener("click", this.run.bind(this));
document.getElementById("save").addEventListener("click", this.save.bind(this));
}
BrowserHandler.prototype.preventDefaults = function (e) {
e.preventDefault();
e.stopPropagation();
};
BrowserHandler.prototype.dragOver = function () {
if (!this.isHighlighted) {
this.dropArea.classList.add("highlight");
this.isHighlighted = true;
}
};
BrowserHandler.prototype.dragDrop = function (e) {
var dt = e.dataTransfer;
var files = dt.files;
this.setSourceFile(files);
if (this.isHighlighted) {
this.dropArea.classList.remove("highlight");
this.isHighlighted = false;
}
};
BrowserHandler.prototype.handleSourceFile = function (e) {
var fs = e.target;
this.setSourceFile(fs.files);
};
BrowserHandler.prototype.setSourceFile = function (fileList) {
if (fileList.length > 0) {
var file = fileList.item(0);
this.processFile(file);
}
};
BrowserHandler.prototype.processFile = function (file) {
var reader = new FileReader();
reader.readAsDataURL(file);
reader.addEventListener("loadend", this.fileLoaded.bind(this));
};
BrowserHandler.prototype.fileLoaded = function (e) {
if (e.type === "loadend") {
var reader = e.target;
var img = document.createElement('img');
img.src = reader.result;
img.addEventListener("load", this.fileParsed.bind(this));
}
};
BrowserHandler.prototype.fileParsed = function (e) {
var img = e.target;
var canvas = document.getElementById("inputcanvas");
var ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
var rgba = ctx.getImageData(0, 0, img.width, img.height).data;
var image = new PAImage(img.width, img.height, 4);
image.setPixels(rgba);
this.instance.setInputImage(image);
document.getElementById("run").classList.remove("hidden");
};
BrowserHandler.prototype.algorithmChanged = function () {
var div = document.getElementById("algorithm").getElementsByTagName("div");
for (var i = 0; i < div.length; ++i) {
if (div[i].hasAttribute("id")) {
if (div[i].getAttribute("id") === this.algorithmElem.value) {
div[i].classList.remove("hidden");
}
else {
div[i].classList.add("hidden");
}
}
}
};
BrowserHandler.prototype.run = function (e) {
e.preventDefault();
e.stopPropagation();
var config = {
algorithm: this.algorithmElem.value
};
var div = document.getElementById(this.algorithmElem.value);
var fields = div.getElementsByTagName("input");
for (var i = 0; i < fields.length; ++i) {
config[fields[i].getAttribute("name")] = fields[i].value;
}
document.getElementById("output").classList.add("running");
document.getElementById("run").classList.add("hidden");
document.getElementById("save").classList.add("hidden");
window.setTimeout(this.instance.run.bind(this.instance, config), 10);
};
BrowserHandler.prototype.clearOutput = function () {
document.getElementById("output").innerHTML = "";
};
BrowserHandler.prototype.appendOutput = function (data) {
var p = document.createElement("p");
p.innerText = data;
document.getElementById("output").append(p);
};
BrowserHandler.prototype.replaceOutput = function (data) {
this.clearOutput();
this.appendOutput(data);
};
BrowserHandler.prototype.completed = function () {
document.getElementById("output").classList.remove("running");
document.getElementById("run").classList.remove("hidden");
document.getElementById("save").classList.remove("hidden");
};
BrowserHandler.prototype.showOutput = function (image) {
var output = document.getElementById("outputcanvas");
output.classList.remove("hidden");
var width = image.getWidth();
var height = image.getHeight();
output.width = width;
output.height = height;
var end = width * height * 3;
var ctx = output.getContext('2d');
var outCanvas = ctx.getImageData(0, 0, width, height);
var outCanvasPixel = outCanvas.data;
var imagePixel = image.getPixels8();
var offsetCanvas = 0;
var offsetImage = 0;
for (var i = 0; i < end; ++i) {
outCanvasPixel[offsetCanvas++] = imagePixel[offsetImage++];
outCanvasPixel[offsetCanvas++] = imagePixel[offsetImage++];
outCanvasPixel[offsetCanvas++] = imagePixel[offsetImage++];
outCanvasPixel[offsetCanvas++] = 255;
}
ctx.putImageData(outCanvas, 0, 0);
};
BrowserHandler.prototype.save = function (e) {
var output = document.getElementById("outputcanvas");
var dataURL = output.toDataURL("image/png");
var a = document.createElement('a');
a.href = dataURL;
a.download = "output.png";
document.body.appendChild(a);
a.click();
};
return BrowserHandler;
}());
document.addEventListener("readystatechange", function (event) {
if (!window.pa) {
window.pa = new PathAnimator();
}
});
var PAImage = (function () {
function PAImage(width, height, channels) {
this.width = width;
this.height = height;
this.channels = channels;
}
PAImage.prototype.allocateMemory = function () {
this.pixels = new Uint8ClampedArray(this.width * this.height * this.channels);
};
PAImage.prototype.setPixels = function (pixels) {
this.pixels = pixels;
};
PAImage.prototype.getPixels8 = function () {
return this.pixels;
};
PAImage.prototype.getWidth = function () {
return this.width;
};
PAImage.prototype.getHeight = function () {
return this.height;
};
PAImage.prototype.getNumChannels = function () {
return this.channels;
};
return PAImage;
}());
;
var PathAnimator = (function () {
function PathAnimator() {
this.inputHandler = new BrowserHandler(this);
this.input = null;
this.output = null;
}
PathAnimator.prototype.setInputImage = function (image) {
this.input = image;
};
PathAnimator.prototype.run = function (config) {
var algorithmName = config['algorithm'];
var algorithm = null;
switch (algorithmName) {
case 'distancepath':
algorithm = new DistancePath(config);
break;
case 'noise':
algorithm = new Noise();
break;
}
if (algorithm) {
this.inputHandler.clearOutput();
this.output = new PAImage(this.input.getWidth(), this.input.getHeight(), 3);
this.output.allocateMemory();
algorithm.setCallback(this);
algorithm.setInput(this.input);
algorithm.setOutput(this.output);
algorithm.run();
this.inputHandler.completed();
this.inputHandler.showOutput(this.output);
}
};
PathAnimator.prototype.messageCallback = function (message, append) {
if (append) {
this.inputHandler.appendOutput(message);
}
else {
this.inputHandler.replaceOutput(message);
}
};
return PathAnimator;
}());
</script>
</body>
</html>