263 lines
7.7 KiB
TypeScript
263 lines
7.7 KiB
TypeScript
/// <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();
|
|
}
|
|
}
|