From 2809a551d7955c976b10fe6d58a1f43a8949a97b Mon Sep 17 00:00:00 2001 From: jdescottes Date: Sat, 14 Jun 2014 14:21:26 +0200 Subject: [PATCH] Fix : Export to GIF : download option In the GIF export panel, user can now choose between : - export online (previous 'upload' feature) - download GIF Labels have been updated in the PNG export panel to follow the same convention. CanvasToBlob library was modified and moved to dedicated utils to handle not only canvas, but also any base64 dateURI. --- src/js/app.js | 117 +++--------------- .../settings/GifExportController.js | 43 ++++--- .../settings/PngExportController.js | 15 +-- src/js/controller/settings/SaveController.js | 2 +- src/js/lib/canvastoblob/canvasToBlob.js | 107 ---------------- src/js/service/GithubStorageService.js | 20 +-- src/js/utils/Base64.js | 51 ++++++++ src/js/utils/ImageToBlob.js | 38 ++++++ src/piskel-script-list.js | 3 +- src/templates/settings/application.html | 4 +- src/templates/settings/export-gif.html | 12 +- src/templates/settings/export-png.html | 4 +- 12 files changed, 152 insertions(+), 264 deletions(-) delete mode 100644 src/js/lib/canvastoblob/canvasToBlob.js create mode 100644 src/js/utils/Base64.js create mode 100644 src/js/utils/ImageToBlob.js diff --git a/src/js/app.js b/src/js/app.js index ae27888c..6d87087e 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -19,7 +19,10 @@ this.shortcutService = new pskl.service.keyboard.ShortcutService(); this.shortcutService.init(); - var size = this.readSizeFromURL_(); + var size = { + height : Constants.DEFAULT.HEIGHT, + width : Constants.DEFAULT.WIDTH + }; var descriptor = new pskl.model.piskel.Descriptor('New Piskel', ''); var piskel = new pskl.model.Piskel(size.width, size.height, descriptor); @@ -111,35 +114,27 @@ this.initTooltips_(); - if (this.isAppEngineVersion) { - this.finishInitAppEngine_(); - } else { - this.finishInitGithub_(); + var piskelData = this.getPiskelInitData_(); + if (piskelData && piskelData.piskel) { + this.loadPiskel_(piskelData.piskel, piskelData.descriptor, piskelData.fps); } }, - finishInitGithub_ : function () { - var framesheetId = this.readFramesheetIdFromURL_(); - if (framesheetId) { - $.publish(Events.SHOW_NOTIFICATION, [{ - "content" : "Loading animation with id : [" + framesheetId + "]" - }]); - this.loadFramesheetFromService(framesheetId); - } + loadPiskel_ : function (serializedPiskel, descriptor, fps) { + pskl.utils.serialization.Deserializer.deserialize(serializedPiskel, function (piskel) { + piskel.setDescriptor(descriptor); + pskl.app.piskelController.setPiskel(piskel); + pskl.app.animationController.setFPS(fps); + }); }, - finishInitAppEngine_ : function () { - if (pskl.appEnginePiskelData_ && pskl.appEnginePiskelData_.piskel) { - pskl.utils.serialization.Deserializer.deserialize(pskl.appEnginePiskelData_.piskel, function (piskel) { - piskel.setDescriptor(pskl.appEnginePiskelData_.descriptor); - pskl.app.piskelController.setPiskel(piskel); - pskl.app.animationController.setFPS(pskl.appEnginePiskelData_.fps); - }); - } + getPiskelInitData_ : function () { + return pskl.appEnginePiskelData_; }, isLoggedIn : function () { - return pskl.appEnginePiskelData_ && pskl.appEnginePiskelData_.isLoggedIn; + var piskelData = this.getPiskelInitData_(); + return piskelData && piskelData.isLoggedIn; }, initTooltips_ : function () { @@ -154,69 +149,6 @@ this.previewFilmController.render(delta); }, - readSizeFromURL_ : function () { - var sizeParam = this.readUrlParameter_("size"); - var size; - // parameter expected as size=64x48 => size=widthxheight - var parts = sizeParam.split("x"); - if (parts && parts.length == 2 && !isNaN(parts[0]) && !isNaN(parts[1])) { - var width = parseInt(parts[0], 10), - height = parseInt(parts[1], 10); - - size = { - height : Math.min(height, Constants.MAX_HEIGHT), - width : Math.min(width, Constants.MAX_WIDTH) - }; - } else { - size = { - height : Constants.DEFAULT.HEIGHT, - width : Constants.DEFAULT.WIDTH - }; - } - return size; - }, - - readFramesheetIdFromURL_ : function () { - return this.readUrlParameter_("frameId"); - }, - - readUrlParameter_ : function (paramName) { - var searchString = window.location.search.substring(1); - var params = searchString.split("&"); - for (var i = 0; i < params.length; i++) { - var param = params[i].split("="); - if (param[0] == paramName) { - return window.unescape(param[1]); - } - } - return ""; - }, - - loadFramesheetFromService : function (frameId) { - var xhr = new XMLHttpRequest(); - xhr.open('GET', Constants.STATIC.URL.GET + '?l=' + frameId, true); - xhr.responseType = 'text'; - xhr.onload = function (e) { - var res = JSON.parse(this.responseText); - pskl.utils.serialization.Deserializer.deserialize(res.framesheet, function (piskel) { - pskl.app.piskelController.setPiskel(piskel); - pskl.app.animationController.setFPS(res.fps); - - $.publish(Events.HIDE_NOTIFICATION); - }); - }; - - xhr.onerror = function () { - $.publish(Events.HIDE_NOTIFICATION); - }; - - xhr.send(); - }, - - store : function (callbacks) { - this.storageService.store(callbacks); - }, - getFirstFrameAsPng : function () { var firstFrame = this.piskelController.getFrameAt(0); var canvasRenderer = new pskl.rendering.CanvasRenderer(firstFrame, 1); @@ -229,21 +161,6 @@ var renderer = new pskl.rendering.PiskelRenderer(this.piskelController); var framesheetCanvas = renderer.renderAsCanvas(); return framesheetCanvas.toDataURL("image/png"); - }, - - uploadAsSpritesheetPNG : function () { - var imageData = this.getFramesheetAsPng(); - this.imageUploadService.upload(imageData, this.openWindow.bind(this)); - }, - - openWindow : function (url) { - var options = [ - "dialog=yes", "scrollbars=no", "status=no", - "width=" + this.piskelController.getWidth() * this.piskelController.getFrameCount(), - "height=" + this.piskelController.getHeight() - ].join(","); - - window.open(url, "piskel-export", options); } }; })(); diff --git a/src/js/controller/settings/GifExportController.js b/src/js/controller/settings/GifExportController.js index 8f5fdeb4..4eaa052a 100644 --- a/src/js/controller/settings/GifExportController.js +++ b/src/js/controller/settings/GifExportController.js @@ -34,8 +34,13 @@ this.previewContainerEl = document.querySelector(".gif-export-preview"); this.radioGroupEl = document.querySelector(".gif-export-radio-group"); - this.uploadForm = $("[name=gif-export-upload-form]"); - this.uploadForm.submit(this.onUploadFormSubmit_.bind(this)); + this.uploadButton = $(".gif-upload-button"); + this.uploadButton.click(this.onUploadButtonClick_.bind(this)); + + this.downloadButton = $(".gif-download-button"); + this.downloadButton.click(this.onDownloadButtonClick_.bind(this)); + + this.exportForm = $(".gif-export-form"); this.exportProgressStatusEl = document.querySelector('.gif-export-progress-status'); this.exportProgressBarEl = document.querySelector('.gif-export-progress-bar'); @@ -43,15 +48,27 @@ this.createRadioElements_(); }; - ns.GifExportController.prototype.onUploadFormSubmit_ = function (evt) { + ns.GifExportController.prototype.onUploadButtonClick_ = function (evt) { evt.originalEvent.preventDefault(); - var selectedZoom = this.getSelectedZoom_(), - fps = this.piskelController.getFPS(), - zoom = selectedZoom; + var zoom = this.getSelectedZoom_(), + fps = this.piskelController.getFPS(); this.renderAsImageDataAnimatedGIF(zoom, fps, this.onGifRenderingCompleted_.bind(this)); }; + ns.GifExportController.prototype.onDownloadButtonClick_ = function (evt) { + var fileName = this.piskelController.getPiskel().getDescriptor().name + '.gif'; + var zoom = this.getSelectedZoom_(), + fps = this.piskelController.getFPS(); + + this.renderAsImageDataAnimatedGIF(zoom, fps, function (imageData) { + pskl.app.imageUploadService.upload(imageData, this.onImageUploadCompleted_.bind(this)); + pskl.utils.ImageToBlob.imageDataToBlob(imageData, "image/gif", function(blob) { + pskl.utils.FileUtils.downloadAsFile(fileName, blob); + }); + }.bind(this)); + }; + ns.GifExportController.prototype.onGifRenderingCompleted_ = function (imageData) { this.updatePreview_(imageData); this.previewContainerEl.classList.add("preview-upload-ongoing"); @@ -62,7 +79,6 @@ this.updatePreview_(imageUrl); this.updateStatus_(imageUrl); this.previewContainerEl.classList.remove("preview-upload-ongoing"); - }; ns.GifExportController.prototype.updatePreview_ = function (src) { @@ -70,7 +86,7 @@ }; ns.GifExportController.prototype.getSelectedZoom_ = function () { - var radiosColl = this.uploadForm.get(0).querySelectorAll("[name=gif-zoom-level]"), + var radiosColl = this.exportForm.get(0).querySelectorAll("[name=gif-zoom-level]"), radios = Array.prototype.slice.call(radiosColl,0); var selectedRadios = radios.filter(function(radio) {return !!radio.checked;}); @@ -105,15 +121,6 @@ return radioEl; }; - ns.GifExportController.prototype.blobToBase64_ = function(blob, cb) { - var reader = new FileReader(); - reader.onload = function() { - var dataUrl = reader.result; - cb(dataUrl); - }; - reader.readAsDataURL(blob); - }; - ns.GifExportController.prototype.renderAsImageDataAnimatedGIF = function(zoom, fps, cb) { var colorCount = pskl.app.currentColorsService.getCurrentColors().length; var preserveColors = colorCount < MAX_GIF_COLORS; @@ -140,7 +147,7 @@ gif.on('finished', function(blob) { this.hideProgressStatus_(); - this.blobToBase64_(blob, cb); + pskl.utils.FileUtils.readFile(blob, cb); }.bind(this)); gif.render(); diff --git a/src/js/controller/settings/PngExportController.js b/src/js/controller/settings/PngExportController.js index 5afba0b9..d0b1fb0e 100644 --- a/src/js/controller/settings/PngExportController.js +++ b/src/js/controller/settings/PngExportController.js @@ -16,21 +16,19 @@ document.querySelector(".zip-generate-button").addEventListener('click', this.onZipButtonClick_.bind(this)); - this.updatePreview_(this.getFramesheetAsBase64Png()); + this.setPreviewSrc_(this.getFramesheetAsCanvas().toDataURL("image/png")); }; ns.PngExportController.prototype.onPngDownloadButtonClick_ = function (evt) { var fileName = this.getPiskelName_() + '.png'; - var renderer = new pskl.rendering.PiskelRenderer(this.piskelController); - var canvas = renderer.renderAsCanvas(); - canvas.toBlob(function(blob) { + pskl.utils.ImageToBlob.canvasToBlob(this.getFramesheetAsCanvas(), function(blob) { pskl.utils.FileUtils.downloadAsFile(fileName, blob); }); }; ns.PngExportController.prototype.onPngUploadButtonClick_ = function (evt) { this.previewContainerEl.classList.add("preview-upload-ongoing"); - pskl.app.imageUploadService.upload(this.getFramesheetAsBase64Png(), this.onImageUploadCompleted_.bind(this)); + pskl.app.imageUploadService.upload(this.getFramesheetAsCanvas().toDataURL("image/png"), this.onImageUploadCompleted_.bind(this)); }; ns.PngExportController.prototype.onZipButtonClick_ = function () { @@ -59,10 +57,9 @@ return this.piskelController.getPiskel().getDescriptor().name; }; - ns.PngExportController.prototype.getFramesheetAsBase64Png = function () { + ns.PngExportController.prototype.getFramesheetAsCanvas = function () { var renderer = new pskl.rendering.PiskelRenderer(this.piskelController); - var framesheetCanvas = renderer.renderAsCanvas(); - return framesheetCanvas.toDataURL("image/png"); + return renderer.renderAsCanvas(); }; ns.PngExportController.prototype.onImageUploadCompleted_ = function (imageUrl) { @@ -84,7 +81,7 @@ } }; - ns.PngExportController.prototype.updatePreview_ = function (src) { + ns.PngExportController.prototype.setPreviewSrc_ = function (src) { this.previewContainerEl.innerHTML = ""; }; diff --git a/src/js/controller/settings/SaveController.js b/src/js/controller/settings/SaveController.js index fc083339..d0cfc38a 100644 --- a/src/js/controller/settings/SaveController.js +++ b/src/js/controller/settings/SaveController.js @@ -49,7 +49,7 @@ this.piskelController.getPiskel().setDescriptor(descriptor); this.beforeSaving_(); - pskl.app.store({ + pskl.app.storageService.store({ success : this.onSaveSuccess_.bind(this), error : this.onSaveError_.bind(this), after : this.afterSaving_.bind(this) diff --git a/src/js/lib/canvastoblob/canvasToBlob.js b/src/js/lib/canvastoblob/canvasToBlob.js deleted file mode 100644 index 6ff79c83..00000000 --- a/src/js/lib/canvastoblob/canvasToBlob.js +++ /dev/null @@ -1,107 +0,0 @@ -/* canvas-toBlob.js - * A canvas.toBlob() implementation. - * 2011-07-13 - * - * By Eli Grey, http://eligrey.com and Devin Samarin, https://github.com/eboyjr - * License: X11/MIT - * See LICENSE.md - */ - -/*global self */ -/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true, - plusplus: true */ - -/*! @source http://purl.eligrey.com/github/canvas-toBlob.js/blob/master/canvas-toBlob.js */ - -(function(view) { -"use strict"; -var - Uint8Array = view.Uint8Array - , HTMLCanvasElement = view.HTMLCanvasElement - , is_base64_regex = /\s*;\s*base64\s*(?:;|$)/i - , base64_ranks - , decode_base64 = function(base64) { - var - len = base64.length - , buffer = new Uint8Array(len / 4 * 3 | 0) - , i = 0 - , outptr = 0 - , last = [0, 0] - , state = 0 - , save = 0 - , rank - , code - , undef - ; - while (len--) { - code = base64.charCodeAt(i++); - rank = base64_ranks[code-43]; - if (rank !== 255 && rank !== undef) { - last[1] = last[0]; - last[0] = code; - save = (save << 6) | rank; - state++; - if (state === 4) { - buffer[outptr++] = save >>> 16; - if (last[1] !== 61 /* padding character */) { - buffer[outptr++] = save >>> 8; - } - if (last[0] !== 61 /* padding character */) { - buffer[outptr++] = save; - } - state = 0; - } - } - } - // 2/3 chance there's going to be some null bytes at the end, but that - // doesn't really matter with most image formats. - // If it somehow matters for you, truncate the buffer up outptr. - return buffer.buffer; - } -; -if (Uint8Array) { - base64_ranks = new Uint8Array([ - 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1 - , -1, -1, 0, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 - , 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 - , -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35 - , 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 - ]); -} -if (HTMLCanvasElement && !HTMLCanvasElement.prototype.toBlob) { - HTMLCanvasElement.prototype.toBlob = function(callback, type /*, ...args*/) { - if (!type) { - type = "image/png"; - } if (this.mozGetAsFile) { - callback(this.mozGetAsFile("canvas", type)); - return; - } - var - args = Array.prototype.slice.call(arguments, 1) - , dataURI = this.toDataURL.apply(this, args) - , header_end = dataURI.indexOf(",") - , data = dataURI.substring(header_end + 1) - , is_base64 = is_base64_regex.test(dataURI.substring(0, header_end)) - , blob - ; - if (Blob.fake) { - // no reason to decode a data: URI that's just going to become a data URI again - blob = new Blob - if (is_base64) { - blob.encoding = "base64"; - } else { - blob.encoding = "URI"; - } - blob.data = data; - blob.size = data.length; - } else if (Uint8Array) { - if (is_base64) { - blob = new Blob([decode_base64(data)], {type: type}); - } else { - blob = new Blob([decodeURIComponent(data)], {type: type}); - } - } - callback(blob); - }; -} -}(self)); \ No newline at end of file diff --git a/src/js/service/GithubStorageService.js b/src/js/service/GithubStorageService.js index 87b34cda..ecbce59c 100644 --- a/src/js/service/GithubStorageService.js +++ b/src/js/service/GithubStorageService.js @@ -8,24 +8,6 @@ ns.GithubStorageService.prototype.init = function () {}; ns.GithubStorageService.prototype.store = function (callbacks) { - var xhr = new XMLHttpRequest(); - var formData = new FormData(); - formData.append('framesheet_content', this.piskelController.serialize()); - formData.append('fps_speed', this.piskelController.getFPS()); - - xhr.open('POST', Constants.STATIC.URL.SAVE, true); - - xhr.onload = function(e) { - if (this.status == 200) { - var baseUrl = window.location.href.replace(window.location.search, ""); - window.location.href = baseUrl + "?frameId=" + this.responseText; - } else { - this.onerror(e); - } - }; - xhr.onerror = function(e) { - $.publish(Events.SHOW_NOTIFICATION, [{"content": "Saving failed ("+this.status+")"}]); - }; - xhr.send(formData); + throw "Github save is no longer available. Use local save instead"; }; })(); \ No newline at end of file diff --git a/src/js/utils/Base64.js b/src/js/utils/Base64.js new file mode 100644 index 00000000..8e8273f8 --- /dev/null +++ b/src/js/utils/Base64.js @@ -0,0 +1,51 @@ +(function () { + var ns = $.namespace('pskl.utils'); + + var base64_ranks; + if (Uint8Array) { + base64_ranks = new Uint8Array([ + 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, + -1, -1, 0, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 + ]); + } + + ns.Base64 = { + decode : function(base64) { + var outptr = 0; + var last = [0, 0]; + var state = 0; + var save = 0; + + var undef; + var len = base64.length, i = 0; + var buffer = new Uint8Array(len / 4 * 3 | 0); + while (len--) { + var code = base64.charCodeAt(i++); + var rank = base64_ranks[code-43]; + if (rank !== 255 && rank !== undef) { + last[1] = last[0]; + last[0] = code; + save = (save << 6) | rank; + state++; + if (state === 4) { + buffer[outptr++] = save >>> 16; + if (last[1] !== 61 /* padding character */) { + buffer[outptr++] = save >>> 8; + } + if (last[0] !== 61 /* padding character */) { + buffer[outptr++] = save; + } + state = 0; + } + } + } + // 2/3 chance there's going to be some null bytes at the end, but that + // doesn't really matter with most image formats. + // If it somehow matters for you, truncate the buffer up outptr. + return buffer.buffer; + } + }; +})(); \ No newline at end of file diff --git a/src/js/utils/ImageToBlob.js b/src/js/utils/ImageToBlob.js new file mode 100644 index 00000000..157c3c64 --- /dev/null +++ b/src/js/utils/ImageToBlob.js @@ -0,0 +1,38 @@ +(function () { + var ns = $.namespace('pskl.utils'); + + var BASE64_REGEX = /\s*;\s*base64\s*(?:;|$)/i; + + ns.ImageToBlob = { + imageDataToBlob : function(dataURI, type, callback) { + var header_end = dataURI.indexOf(","), + data = dataURI.substring(header_end + 1), + isBase64 = BASE64_REGEX.test(dataURI.substring(0, header_end)), + blob; + + if (Blob.fake) { + // no reason to decode a data: URI that's just going to become a data URI again + blob = new Blob(); + blob.encoding = isBase64 ? "base64" : "URI"; + blob.data = data; + blob.size = data.length; + } else if (Uint8Array) { + var blobData = isBase64 ? pskl.utils.Base64.decode(data) : decodeURIComponent(data); + blob = new Blob([blobData], {type: type}); + } + callback(blob); + }, + + canvasToBlob : function(canvas, callback, type /*, ...args*/) { + type = type || "image/png"; + + if (this.mozGetAsFile) { + callback(this.mozGetAsFile("canvas", type)); + } else { + var args = Array.prototype.slice.call(arguments, 2); + var dataURI = this.toDataURL.apply(this, args); + pskl.utils.ImageToBlob.imageDataToBlob(dataURI, type, callback); + } + } + }; +})(); \ No newline at end of file diff --git a/src/piskel-script-list.js b/src/piskel-script-list.js index c3f4c034..2b4dacbe 100644 --- a/src/piskel-script-list.js +++ b/src/piskel-script-list.js @@ -11,7 +11,6 @@ // JSZip https://github.com/Stuk/jszip "js/lib/jszip/jszip.min.js", - "js/lib/canvastoblob/canvasToBlob.js", // Spectrum color-picker library "js/lib/spectrum/spectrum.js", @@ -24,12 +23,14 @@ // Libraries "js/utils/core.js", "js/utils/UserAgent.js", + "js/utils/Base64.js", "js/utils/CanvasUtils.js", "js/utils/Dom.js", "js/utils/Math.js", "js/utils/FileUtils.js", "js/utils/FrameUtils.js", "js/utils/LayerUtils.js", + "js/utils/ImageToBlob.js", "js/utils/ImageResizer.js", "js/utils/PixelUtils.js", "js/utils/Template.js", diff --git a/src/templates/settings/application.html b/src/templates/settings/application.html index fdde7954..ed2b07bc 100644 --- a/src/templates/settings/application.html +++ b/src/templates/settings/application.html @@ -29,9 +29,9 @@ -
+
diff --git a/src/templates/settings/export-gif.html b/src/templates/settings/export-gif.html index b95c991a..525fa1cc 100644 --- a/src/templates/settings/export-gif.html +++ b/src/templates/settings/export-gif.html @@ -3,17 +3,19 @@ Export to Animated GIF
- -
+ +
- - -
+ + +
+ +
diff --git a/src/templates/settings/export-png.html b/src/templates/settings/export-png.html index d4611dac..08c9ee66 100644 --- a/src/templates/settings/export-png.html +++ b/src/templates/settings/export-png.html @@ -6,8 +6,8 @@ Preview :
- - + +