/// /// /** * 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 = 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 = 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 = e.target; let img = document.createElement('img'); img.src = 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 = e.target; const canvas = 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 = 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 = 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(); } }