initial javascript version
parent
90e65cacd6
commit
e3b55ac4fa
|
@ -3,3 +3,7 @@ build
|
||||||
.project
|
.project
|
||||||
.settings/
|
.settings/
|
||||||
c++/html
|
c++/html
|
||||||
|
inbrowser/.tscache
|
||||||
|
inbrowser/jsdoc
|
||||||
|
inbrowser/node_modules
|
||||||
|
inbrowser/tmp
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
module.exports = function(grunt) {
|
||||||
|
|
||||||
|
// Project configuration.
|
||||||
|
grunt.initConfig({
|
||||||
|
pkg: grunt.file.readJSON('package.json'),
|
||||||
|
ts: {
|
||||||
|
default: {
|
||||||
|
src: ['ts/**/*.ts'],
|
||||||
|
outDir: 'tmp/',
|
||||||
|
options: {
|
||||||
|
module: 'none',
|
||||||
|
sourceMap: false,
|
||||||
|
target: 'es5',
|
||||||
|
rootDir: 'ts/'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
concat: {
|
||||||
|
js: {
|
||||||
|
src: ['tmp/**/*.js'],
|
||||||
|
dest: 'tmp/bootstrap.out'
|
||||||
|
},
|
||||||
|
dist: {
|
||||||
|
src: ['html/index.html'],
|
||||||
|
dest: 'webroot/index.html',
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
process: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
jsbootstrapts: {
|
||||||
|
files: ['ts/**/*.ts'],
|
||||||
|
tasks: ['ts', 'concat']
|
||||||
|
},
|
||||||
|
html: {
|
||||||
|
files: ['html/*'],
|
||||||
|
tasks: ['concat']
|
||||||
|
},
|
||||||
|
lessdefault: {
|
||||||
|
files: ['less/*.less'],
|
||||||
|
tasks: ['less:default', 'concat']
|
||||||
|
},
|
||||||
|
},
|
||||||
|
less: {
|
||||||
|
default: {
|
||||||
|
options: {
|
||||||
|
"strictImports": true,
|
||||||
|
"compress": true
|
||||||
|
},
|
||||||
|
files: {
|
||||||
|
"tmp/default.css": "less/main.less",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||||
|
grunt.loadNpmTasks('grunt-contrib-less');
|
||||||
|
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||||
|
grunt.loadNpmTasks("grunt-ts");
|
||||||
|
|
||||||
|
// Default task(s).
|
||||||
|
grunt.registerTask('default', ['less', 'ts', 'concat', 'watch']);
|
||||||
|
grunt.registerTask('release', ['less', 'ts', 'concat']);
|
||||||
|
|
||||||
|
};
|
|
@ -0,0 +1,56 @@
|
||||||
|
<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">
|
||||||
|
<%= grunt.file.read('tmp/default.css') %>
|
||||||
|
</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">
|
||||||
|
<%= grunt.file.read('tmp/bootstrap.out') %>
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,66 @@
|
||||||
|
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;
|
||||||
|
&.highlight {
|
||||||
|
border-color: purple;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 10px;
|
||||||
|
color:#000;
|
||||||
|
background: #ccc;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
&:hover {
|
||||||
|
background: #ddd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#fileElem {
|
||||||
|
display: none;
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "pathanimation",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Javascript version of the Path Animation tool",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "GrumpyDeveloper https://contentnation.net/grumpydevelop",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"grunt": "^1.4.1",
|
||||||
|
"grunt-contrib-less": "^3.0.0",
|
||||||
|
"grunt-contrib-watch": "^1.1.0",
|
||||||
|
"grunt-ts": "^6.0.0-beta.22",
|
||||||
|
"grunt-contrib-concat": "^2.0.0"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,262 @@
|
||||||
|
/// <reference path="PAImage.ts" />
|
||||||
|
/// <reference path="PathAnimator.ts" />
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functionality to handle browser io
|
||||||
|
*/
|
||||||
|
class BrowserHandler {
|
||||||
|
/**
|
||||||
|
* Drop area for file uploads
|
||||||
|
*/
|
||||||
|
private dropArea: HTMLElement;
|
||||||
|
/**
|
||||||
|
* Main instace of application
|
||||||
|
*/
|
||||||
|
private instance: PathAnimator;
|
||||||
|
/**
|
||||||
|
* Indicator flag if the drop area is highlighted
|
||||||
|
*/
|
||||||
|
private isHighlighted: boolean;
|
||||||
|
/**
|
||||||
|
* Algrothm selection dropdown element
|
||||||
|
*/
|
||||||
|
private algorithmElem: HTMLSelectElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
constructor(instance: PathAnimator) {
|
||||||
|
this.instance = instance;
|
||||||
|
this.isHighlighted = false;
|
||||||
|
this.dropArea = document.getElementById('drop-area');
|
||||||
|
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(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 = <HTMLSelectElement>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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent default action for given event
|
||||||
|
* @param e incoming event
|
||||||
|
*/
|
||||||
|
preventDefaults (e: Event) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event when something is dragged over out drag/drop area
|
||||||
|
*/
|
||||||
|
dragOver() {
|
||||||
|
if (!this.isHighlighted) {
|
||||||
|
this.dropArea.classList.add("highlight");
|
||||||
|
this.isHighlighted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Something was droppen on our drag/drop area
|
||||||
|
* @param e Incoming event
|
||||||
|
*/
|
||||||
|
dragDrop(e: DragEvent) {
|
||||||
|
let dt = e.dataTransfer
|
||||||
|
let files = dt.files
|
||||||
|
this.setSourceFile(files);
|
||||||
|
if (this.isHighlighted) {
|
||||||
|
this.dropArea.classList.remove("highlight");
|
||||||
|
this.isHighlighted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A file was selected via upload file button
|
||||||
|
* @param e: Incoming event
|
||||||
|
*/
|
||||||
|
handleSourceFile(e: Event) {
|
||||||
|
var fs = <HTMLInputElement>e.target;
|
||||||
|
this.setSourceFile(fs.files);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the source file and trigger processing with given file list. Only the first file will be used
|
||||||
|
* @param fileList the file list generated by the browser on drop or file selection
|
||||||
|
*/
|
||||||
|
setSourceFile(fileList: FileList) {
|
||||||
|
// we only support one image
|
||||||
|
if (fileList.length > 0) {
|
||||||
|
var file = fileList.item(0);
|
||||||
|
this.processFile(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process (load) given file, calls fileLoaded when read
|
||||||
|
* @param file file to process
|
||||||
|
*/
|
||||||
|
processFile(file: File) {
|
||||||
|
let reader = new FileReader()
|
||||||
|
reader.readAsDataURL(file)
|
||||||
|
reader.addEventListener("loadend", this.fileLoaded.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file loading from processFile has been finished.
|
||||||
|
* fileParsed will be called after image was rendered
|
||||||
|
* @param e progress event (normally file loaded)
|
||||||
|
*/
|
||||||
|
fileLoaded(e:ProgressEvent) {
|
||||||
|
if (e.type === "loadend") { // sanity check
|
||||||
|
var reader = <FileReader>e.target;
|
||||||
|
let img = <HTMLImageElement>document.createElement('img');
|
||||||
|
img.src = <string>reader.result;
|
||||||
|
img.addEventListener("load", this.fileParsed.bind(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The image data from file is ready.
|
||||||
|
* The canvas is filled with the image and the pixel data is extracted
|
||||||
|
* and set for the main processing instance.
|
||||||
|
* @param e incoming event
|
||||||
|
*/
|
||||||
|
fileParsed(e: Event) {
|
||||||
|
var img = <HTMLImageElement>e.target;
|
||||||
|
const canvas = <HTMLCanvasElement>document.getElementById("inputcanvas");
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
canvas.width = img.width;
|
||||||
|
canvas.height = img.height;
|
||||||
|
ctx.drawImage(img, 0, 0);
|
||||||
|
const 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The algrothm select dropdown was changed.
|
||||||
|
* Show and hide algrothm specific inputs
|
||||||
|
*/
|
||||||
|
algorithmChanged() {
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The run button was pressed.
|
||||||
|
* The algorithm specific setting are collected and an async timeout call is triggered to run the PathAnimator run function
|
||||||
|
* @param e incoming Event
|
||||||
|
*/
|
||||||
|
run(e: Event) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
// collect algorithm param
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear logging output
|
||||||
|
*/
|
||||||
|
clearOutput() {
|
||||||
|
document.getElementById("output").innerHTML = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append message to logging output
|
||||||
|
* @param data string to display
|
||||||
|
*/
|
||||||
|
appendOutput(data: string) {
|
||||||
|
var p = document.createElement("p");
|
||||||
|
p.innerText = data;
|
||||||
|
document.getElementById("output").append(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace old logging output by new data
|
||||||
|
* @param data new string to display
|
||||||
|
*/
|
||||||
|
replaceOutput(data: string) {
|
||||||
|
this.clearOutput();
|
||||||
|
this.appendOutput(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processing has completed
|
||||||
|
* Show buttons and hide running animation
|
||||||
|
*/
|
||||||
|
completed() {
|
||||||
|
document.getElementById("output").classList.remove("running");
|
||||||
|
document.getElementById("run").classList.remove("hidden");
|
||||||
|
document.getElementById("save").classList.remove("hidden");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show output of the algorithm
|
||||||
|
* The Canvas is filled with the output of the algorithm.
|
||||||
|
* Preprocessing is needed, the algorithm exports RGB data, the canvas wants RGBA.
|
||||||
|
* @param image image to show
|
||||||
|
*/
|
||||||
|
showOutput(image: PAImage) {
|
||||||
|
var output = <HTMLCanvasElement>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;
|
||||||
|
const ctx = output.getContext('2d');
|
||||||
|
var outCanvas = ctx.getImageData(0,0, width, height);
|
||||||
|
const 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User has clicked the save button
|
||||||
|
* Trigger download
|
||||||
|
* @param e incoming Event
|
||||||
|
*/
|
||||||
|
save(e: Event) {
|
||||||
|
var output = <HTMLCanvasElement>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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
/**
|
||||||
|
* Wrapper for pixel data of an image
|
||||||
|
*/
|
||||||
|
class PAImage {
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param width width in pixel
|
||||||
|
* @param height height in pixel
|
||||||
|
* @param channels number of channels
|
||||||
|
*/
|
||||||
|
constructor(width:number, height:number, channels: number) {
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.channels = channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate memory for image data
|
||||||
|
*/
|
||||||
|
allocateMemory() {
|
||||||
|
this.pixels = new Uint8ClampedArray(this.width * this. height * this.channels);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set pixel data
|
||||||
|
* @param pixels pixel data
|
||||||
|
*/
|
||||||
|
setPixels(pixels : Uint8ClampedArray) {
|
||||||
|
this.pixels = pixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get pixel data
|
||||||
|
* @return pixel data
|
||||||
|
*/
|
||||||
|
getPixels8() : Uint8ClampedArray {
|
||||||
|
return this.pixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get width of image
|
||||||
|
* @return width of image
|
||||||
|
*/
|
||||||
|
getWidth() : number {
|
||||||
|
return this.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get height of image
|
||||||
|
* @return height of image
|
||||||
|
*/
|
||||||
|
getHeight() : number {
|
||||||
|
return this.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get number of channels
|
||||||
|
* @return number of channels
|
||||||
|
*/
|
||||||
|
getNumChannels() : number {
|
||||||
|
return this.channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image width
|
||||||
|
*/
|
||||||
|
private width: number;
|
||||||
|
/**
|
||||||
|
* Image height
|
||||||
|
*/
|
||||||
|
private height: number;
|
||||||
|
/**
|
||||||
|
* Number of channels
|
||||||
|
*/
|
||||||
|
private channels: number;
|
||||||
|
/**
|
||||||
|
* Image data as RGBA 8 bit per channel
|
||||||
|
*/
|
||||||
|
private pixels: Uint8ClampedArray;
|
||||||
|
};
|
|
@ -0,0 +1,81 @@
|
||||||
|
/// <reference path="BrowserHandler.ts" />
|
||||||
|
/// <reference path="window.ts" />
|
||||||
|
/// <reference path="PAImage.ts" />
|
||||||
|
/// <reference path="algorithm/DistancePath.ts" />
|
||||||
|
/// <reference path="algorithm/Noise.ts" />
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main application class
|
||||||
|
*/
|
||||||
|
class PathAnimator {
|
||||||
|
/**
|
||||||
|
* Browser abstraction instance
|
||||||
|
*/
|
||||||
|
private inputHandler:BrowserHandler;
|
||||||
|
/**
|
||||||
|
* Input image
|
||||||
|
*/
|
||||||
|
private input: PAImage;
|
||||||
|
/**
|
||||||
|
* Output image
|
||||||
|
*/
|
||||||
|
private output: PAImage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.inputHandler = new BrowserHandler(this);
|
||||||
|
this.input = null;
|
||||||
|
this.output = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set input image
|
||||||
|
* @param image image to set as input
|
||||||
|
*/
|
||||||
|
setInputImage(image: PAImage) {
|
||||||
|
this.input = image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actual run function that triggerse the processing.
|
||||||
|
* @param config the algorithm specific config options
|
||||||
|
*/
|
||||||
|
run(config: Object) {
|
||||||
|
var algorithmName = config['algorithm'];
|
||||||
|
var algorithm: 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We got a message from out algorithm, display it
|
||||||
|
* @param message Message to show
|
||||||
|
* @param append True if message should be appended, false if previous should be cleared
|
||||||
|
*/
|
||||||
|
messageCallback(message: string, append: boolean) {
|
||||||
|
if (append) {
|
||||||
|
this.inputHandler.appendOutput(message);
|
||||||
|
} else {
|
||||||
|
this.inputHandler.replaceOutput(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
/// <reference path="../PathAnimator.ts" />
|
||||||
|
/// <reference path="../PAImage.ts" />
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for the algorithms
|
||||||
|
*/
|
||||||
|
class Algorithm {
|
||||||
|
/**
|
||||||
|
* Main instance
|
||||||
|
*/
|
||||||
|
protected callback: PathAnimator;
|
||||||
|
/**
|
||||||
|
* Input image
|
||||||
|
*/
|
||||||
|
protected input: PAImage;
|
||||||
|
/**
|
||||||
|
* Output image
|
||||||
|
*/
|
||||||
|
protected output: PAImage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.callback = null;
|
||||||
|
this.output = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the callback instance
|
||||||
|
* @param callback The main PathAnimator instance
|
||||||
|
*/
|
||||||
|
setCallback(callback: PathAnimator) {
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the input image
|
||||||
|
* @param image Input image
|
||||||
|
*/
|
||||||
|
setInput(image: PAImage) {
|
||||||
|
this.input = image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the output image
|
||||||
|
* @param image Output image
|
||||||
|
*/
|
||||||
|
setOutput(image: PAImage) {
|
||||||
|
this.output = image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dummy run function, must be overloaded
|
||||||
|
*/
|
||||||
|
run() {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,158 @@
|
||||||
|
/// <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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
/// <reference path="Algorithm.ts" />
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demo algorithm that fills image with noise
|
||||||
|
*/
|
||||||
|
class Noise extends Algorithm {
|
||||||
|
/**
|
||||||
|
* Run the algorithm
|
||||||
|
*/
|
||||||
|
run() {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
/// <reference path="PathAnimator.ts" />
|
||||||
|
/**
|
||||||
|
* Main entry function
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// create our class as soon as the document is loaded
|
||||||
|
document.addEventListener("readystatechange", function(event: Event) {
|
||||||
|
if (!window.pa) {
|
||||||
|
window.pa = new PathAnimator();
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,11 @@
|
||||||
|
/// <reference path="PathAnimator.ts" />
|
||||||
|
|
||||||
|
/**
|
||||||
|
* extend Window to make our global instance available (for debugging purposes)
|
||||||
|
*/
|
||||||
|
interface Window {
|
||||||
|
/**
|
||||||
|
* our instance
|
||||||
|
*/
|
||||||
|
pa: PathAnimator;
|
||||||
|
}
|
|
@ -0,0 +1,462 @@
|
||||||
|
<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>
|
Loading…
Reference in New Issue