var messages = { is_processing: "Running semantic segmentation...", upload_failed: "Error attempting to upload the file.", upload_cancelled: "Upload cancelled or browser dropped connection.", unable_to_compute: "We're sorry! We were unable to compute your image.", pending: "Sending to Generative Adversarial Network...", complete: "Processing complete!", } var upload = (function(){ var upload = {} var uploading = false var MAX_SIDE = 512 upload.init = function(){ upload.bind() } upload.bind = function(){ $("input[type=file]").on('change', upload.change) $("#upload_btn").on('click', upload.go) document.body.addEventListener("dragover", upload.dragover) document.body.addEventListener("dragleave", upload.dragover) document.body.addEventListener("drop", upload.change) } upload.dragover = function(e){ e.stopPropagation() e.preventDefault() } upload.change = function(e){ e.preventDefault() var files = e.dataTransfer ? e.dataTransfer.files : e.target.files if (files.length) { var file = files[files.length - 1] if (!file.type.match('image.*')) return var reader = new FileReader() reader.onload = onReaderLoad reader.readAsDataURL(file) } function onReaderLoad(e) { // Don't leak! reader.onload = null var img = new Image img.onload = function(){ img.onload = null upload.ready(img) } img.src = e.target.result } } upload.ready = function(img){ var resized = renderToCanvas(img, { correctOrientation: true }) var canvas = document.querySelector('#user_photo_canvas') ctx = canvas.getContext('2d') ctx.fillStyle = 'black' ctx.fillRect(0, 0, MAX_SIDE, MAX_SIDE) var x_offset = (MAX_SIDE - resized.width) / 2 var y_offset = (MAX_SIDE - resized.height) / 2 ctx.drawImage(resized, x_offset, y_offset) app.didPickPhoto() } upload.go = function(){ if (uploading) return uploading = true app.didClickUpload() try { var canvas = document.querySelector('#user_photo_canvas') var cb = canvas.toBlob(function(blob){ upload.send(blob) }, 'image/jpeg', 89) } catch(e){ app.updateProgress(messages.unable_to_compute) } } upload.send = function(blob){ console.log("sending upload...") var fd = new FormData() fd.append('user_image', blob) fd.append('ext', 'jpg') fd.append('style', $("#dropdown").val()) fd.append('agree', $("#agree").val() || 0) var xhr = new XMLHttpRequest() xhr.upload.addEventListener("progress", upload.progress, false) xhr.addEventListener("load", upload.complete, false) xhr.addEventListener("error", upload.failed, false) xhr.addEventListener("abort", upload.canceled, false) xhr.open("POST", "/upload") xhr.send(fd) } upload.progress = function (e) { if (e.lengthComputable) { var percentComplete = Math.round(e.loaded * 100 / e.total) if (percentComplete > 99) { app.updateProgress(messages.is_processing) } else { app.updateProgress("Uploaded " + percentComplete.toString() + '%') } } else { app.updateProgress(messages.unable_to_compute) } } upload.complete = function (e) { uploading = false try { var data = JSON.parse(e.target.responseText) } catch (e) { return app.updateProgress(messages.upload_failed) } app.uploadDidComplete() upload.data = data upload.task_progress(data.task_url) } upload.failed = function (evt) { uploading = false app.updateProgress(messages.upload_failed) } upload.cancelled = function (evt) { uploading = false app.updateProgress(messages.upload_cancelled) } upload.task_progress = function (status_url) { var is_public = $("#agree").val() || 0 var uuid = upload.data.uuid $.getJSON(status_url, function(data){ console.log(data) var alive = true var delay = 500 switch(data.state) { case 'PENDING': app.updateProgress(messages.pending) delay = 2000 break case 'PROCESSING': app.updateProgress(data.message, data.percent) delay = 500 break case 'SUCCESS': app.updateProgress(messages.complete) if (is_public) { history.pushState({}, 'DullDream', '/d/' + uuid) } else { history.pushState({}, 'DullDream', '/p/' + uuid) } app.processingComplete(uuid, is_public) // truthy if private alive = false break default: // NB: error state alive = false break } if (alive) { setTimeout(function() { upload.task_progress(status_url) }, delay) } }) } function renderToCanvas(img, options) { if (!img) return options = options || {} // Canvas max size for any side var maxSize = MAX_SIDE var canvas = document.createElement('canvas') var ctx = canvas.getContext('2d') var initialScale = options.scale || 1 // Scale to needed to constrain canvas to max size var scale = getScale(img.width * initialScale, img.height * initialScale, maxSize, maxSize, true) // Still need to apply the user defined scale scale *= initialScale var width = canvas.width = Math.round(img.width * scale) var height = canvas.height = Math.round(img.height * scale) var correctOrientation = options.correctOrientation var jpeg = !!img.src.match(/data:image\/jpeg|\.jpeg$|\.jpg$/i) var hasDataURI = !!img.src.match(/^data:/) ctx.save() // Can only correct orientation on JPEGs represented as dataURIs // for the time being if (correctOrientation && jpeg && hasDataURI) { applyOrientationCorrection(canvas, ctx, img.src) } // Resize image if too large if (scale !== 1) { ctx.scale(scale, scale) } ctx.drawImage(img, 0, 0) ctx.restore() return canvas } function getScale(width, height, viewportWidth, viewportHeight, fillViewport) { fillViewport = !!fillViewport var landscape = (width / height) > (viewportWidth / viewportHeight) if (landscape) { if (fillViewport) { return fitVertical() } else if (width > viewportWidth) { return fitHorizontal() } } else { if (fillViewport) { return fitHorizontal() } else if (height > viewportHeight) { return fitVertical() } } return 1 function fitHorizontal() { return viewportWidth / width } function fitVertical() { return viewportHeight / height } } function applyOrientationCorrection(canvas, ctx, uri) { var orientation = getOrientation(uri) // Only apply transform if there is some non-normal orientation if (orientation && orientation !== 1) { var transform = orientationToTransform[orientation] var rotation = transform.rotation var mirror = transform.mirror var flipAspect = rotation === 90 || rotation === 270 if (flipAspect) { // Fancy schmancy swap algo canvas.width = canvas.height + canvas.width canvas.height = canvas.width - canvas.height canvas.width -= canvas.height } if (rotation > 0) { applyRotation(canvas, ctx, rotation) } } } function applyRotation(canvas, ctx, deg) { var radians = deg * (Math.PI / 180) if (deg === 90) { ctx.translate(canvas.width, 0) } else if (deg === 180) { ctx.translate(canvas.width, canvas.height) } else if (deg == 270) { ctx.translate(0, canvas.height) } ctx.rotate(radians) } function getOrientation (uri) { var exif = new ExifReader // Split off the base64 data var base64String = uri.split(',')[1] // Read off first 128KB, which is all we need to // get the EXIF data var arr = base64ToUint8Array(base64String, 0, Math.pow(2, 17)) try { exif.load(arr.buffer) return exif.getTagValue('Orientation') } catch (err) { return 1 } } function base64ToUint8Array(string, start, finish) { var start = start || 0 var finish = finish || string.length // atob that shit var binary = atob(string) var buffer = new Uint8Array(binary.length) for (var i = start; i < finish; i++) { buffer[i] = binary.charCodeAt(i) } return buffer } /** * Mapping from EXIF orientation values to data * regarding the rotation and mirroring necessary to * render the canvas correctly * Derived from: * http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto/ */ var orientationToTransform = { 1: { rotation: 0, mirror: false }, 2: { rotation: 0, mirror: true }, 3: { rotation: 180, mirror: false }, 4: { rotation: 180, mirror: true }, 5: { rotation: 90, mirror: true }, 6: { rotation: 90, mirror: false }, 7: { rotation: 270, mirror: true }, 8: { rotation: 270, mirror: false } } return upload })()