From 73986c4e617032b8b3766dc116983c3cdd248d5b Mon Sep 17 00:00:00 2001 From: Julian Descottes Date: Sun, 16 Oct 2016 18:28:12 +0200 Subject: [PATCH] move arraybuffer serializer to dedicated subfolder --- src/js/Constants.js | 2 +- src/js/controller/piskel/PiskelController.js | 2 +- src/js/service/BackupService.js | 2 +- src/js/service/HistoryService.js | 8 +- .../service/storage/DesktopStorageService.js | 2 +- .../storage/FileDownloadStorageService.js | 2 +- .../service/storage/GalleryStorageService.js | 2 +- src/js/service/storage/LocalStorageService.js | 2 +- src/js/utils/serialization/Deserializer.js | 181 ++++++----------- src/js/utils/serialization/Serializer.js | 191 +++--------------- .../utils/serialization/StringSerializer.js | 34 ---- .../arraybuffer/ArrayBufferDeserializer.js | 113 +++++++++++ .../arraybuffer/ArrayBufferSerializer.js | 175 ++++++++++++++++ .../serialization/backward/Deserializer_v2.js | 79 -------- src/piskel-script-list.js | 4 +- test/js/service/HistoryServiceTest.js | 11 +- test/js/utils/serialization/SerializerTest.js | 4 +- 17 files changed, 392 insertions(+), 422 deletions(-) delete mode 100644 src/js/utils/serialization/StringSerializer.js create mode 100644 src/js/utils/serialization/arraybuffer/ArrayBufferDeserializer.js create mode 100644 src/js/utils/serialization/arraybuffer/ArrayBufferSerializer.js delete mode 100644 src/js/utils/serialization/backward/Deserializer_v2.js diff --git a/src/js/Constants.js b/src/js/Constants.js index 54fe8362..f6ac4519 100644 --- a/src/js/Constants.js +++ b/src/js/Constants.js @@ -7,7 +7,7 @@ var Constants = { LAYER_OPACITY : 0.2 }, - MODEL_VERSION : 3, + MODEL_VERSION : 2, MAX_HEIGHT : 1024, MAX_WIDTH : 1024, diff --git a/src/js/controller/piskel/PiskelController.js b/src/js/controller/piskel/PiskelController.js index 3b987322..b03fb30a 100644 --- a/src/js/controller/piskel/PiskelController.js +++ b/src/js/controller/piskel/PiskelController.js @@ -279,6 +279,6 @@ }; ns.PiskelController.prototype.serialize = function () { - return pskl.utils.Serializer.serializePiskel(this.piskel); + return pskl.utils.serialization.Serializer.serialize(this.piskel); }; })(); diff --git a/src/js/service/BackupService.js b/src/js/service/BackupService.js index f4f0a3c0..5e39601f 100644 --- a/src/js/service/BackupService.js +++ b/src/js/service/BackupService.js @@ -34,7 +34,7 @@ // Do not save an unchanged piskel if (hash !== this.lastHash) { this.lastHash = hash; - var serializedPiskel = pskl.utils.StringSerializer.serializePiskel(piskel); + var serializedPiskel = pskl.utils.serialization.Serializer.serialize(piskel); this.savePiskel_('next', serializedPiskel, JSON.stringify(info)); } }; diff --git a/src/js/service/HistoryService.js b/src/js/service/HistoryService.js index 79746830..debfd2a3 100644 --- a/src/js/service/HistoryService.js +++ b/src/js/service/HistoryService.js @@ -1,10 +1,11 @@ (function () { var ns = $.namespace('pskl.service'); - ns.HistoryService = function (piskelController, shortcutService, deserializer) { + ns.HistoryService = function (piskelController, shortcutService, deserializer, serializer) { this.piskelController = piskelController || pskl.app.piskelController; this.shortcutService = shortcutService || pskl.app.shortcutService; - this.deserializer = deserializer || pskl.utils.serialization.Deserializer; + this.deserializer = deserializer || pskl.utils.serialization.arraybuffer.ArrayBufferDeserializer; + this.serializer = serializer || pskl.utils.serialization.arraybuffer.ArrayBufferSerializer; this.stateQueue = []; this.currentIndex = -1; @@ -53,7 +54,8 @@ var isSnapshot = action.type === ns.HistoryService.SNAPSHOT; var isAtAutoSnapshotInterval = this.currentIndex % ns.HistoryService.SNAPSHOT_PERIOD === 0; if (isSnapshot || isAtAutoSnapshotInterval) { - state.piskel = this.piskelController.serialize(); + var piskel = this.piskelController.getPiskel(); + state.piskel = this.serializer.serialize(piskel); } this.stateQueue.push(state); diff --git a/src/js/service/storage/DesktopStorageService.js b/src/js/service/storage/DesktopStorageService.js index 5b4d5220..6ceab17c 100644 --- a/src/js/service/storage/DesktopStorageService.js +++ b/src/js/service/storage/DesktopStorageService.js @@ -24,7 +24,7 @@ return Q.reject('Invalid file name'); } - var serialized = pskl.utils.StringSerializer.serializePiskel(piskel); + var serialized = pskl.utils.serialization.Serializer.serialize(piskel); savePath = this.addExtensionIfNeeded_(savePath); piskel.savePath = savePath; piskel.setName(this.extractFilename_(savePath)); diff --git a/src/js/service/storage/FileDownloadStorageService.js b/src/js/service/storage/FileDownloadStorageService.js index bdc5ef00..c21c0856 100644 --- a/src/js/service/storage/FileDownloadStorageService.js +++ b/src/js/service/storage/FileDownloadStorageService.js @@ -5,7 +5,7 @@ ns.FileDownloadStorageService.prototype.init = function () {}; ns.FileDownloadStorageService.prototype.save = function (piskel) { - var serialized = pskl.utils.StringSerializer.serializePiskel(piskel, false); + var serialized = pskl.utils.serialization.Serializer.serialize(piskel); var deferred = Q.defer(); pskl.utils.BlobUtils.stringToBlob(serialized, function(blob) { diff --git a/src/js/service/storage/GalleryStorageService.js b/src/js/service/storage/GalleryStorageService.js index 809fe63f..030f0bb1 100644 --- a/src/js/service/storage/GalleryStorageService.js +++ b/src/js/service/storage/GalleryStorageService.js @@ -11,7 +11,7 @@ var descriptor = piskel.getDescriptor(); var deferred = Q.defer(); - var serialized = pskl.utils.StringSerializer.serializePiskel(piskel); + var serialized = pskl.utils.serialization.Serializer.serialize(piskel); var data = { framesheet : serialized, diff --git a/src/js/service/storage/LocalStorageService.js b/src/js/service/storage/LocalStorageService.js index ab71c29c..8e6fe512 100644 --- a/src/js/service/storage/LocalStorageService.js +++ b/src/js/service/storage/LocalStorageService.js @@ -14,7 +14,7 @@ var name = piskel.getDescriptor().name; var description = piskel.getDescriptor().description; - var serialized = pskl.utils.StringSerializer.serializePiskel(piskel); + var serialized = pskl.utils.serialization.Serializer.serialize(piskel); if (pskl.app.localStorageService.getPiskel(name)) { var confirmOverwrite = window.confirm('There is already a piskel saved as ' + name + '. Overwrite ?'); if (!confirmOverwrite) { diff --git a/src/js/utils/serialization/Deserializer.js b/src/js/utils/serialization/Deserializer.js index 0335e4f4..0a7769b6 100644 --- a/src/js/utils/serialization/Deserializer.js +++ b/src/js/utils/serialization/Deserializer.js @@ -2,43 +2,18 @@ var ns = $.namespace('pskl.utils.serialization'); ns.Deserializer = function (data, callback) { + this.layersToLoad_ = 0; this.data_ = data; this.callback_ = callback; + this.piskel_ = null; + this.layers_ = []; }; ns.Deserializer.deserialize = function (data, callback) { - var modelVersion; - - var isJSON = false; - if (data instanceof ArrayBuffer || data instanceof Array) { - var uint8 = new Uint8Array(data); - - // Backward compatibility for modelVersion < 3 for LocalStorage, FileImport etc... which - // now always serve the serialized sprites as strings and no longer as objects - if (String.fromCharCode(uint8[0]) == '{') { - data = ''; - for (var i = 0 ; i < uint8.length ; i++) { - data += String.fromCharCode(uint8[i]); - } - data = JSON.parse(data); - modelVersion = data.modelVersion; - } else { - var arr16 = new Uint16Array(uint8.buffer); - modelVersion = arr16[0]; - } - } else if (typeof data == 'object') { - // Backward Compatibility for sprites served from piskelapp.com with modelVersion < 3 - modelVersion = data.modelVersion; - } else { - throw 'Invalid data for deserializing: ' + data; - } - var deserializer; - if (modelVersion == Constants.MODEL_VERSION) { + if (data.modelVersion == Constants.MODEL_VERSION) { deserializer = new ns.Deserializer(data, callback); - } else if (modelVersion == 2) { - deserializer = new ns.backward.Deserializer_v2(data, callback); - } else if (modelVersion == 1) { + } else if (data.modelVersion == 1) { deserializer = new ns.backward.Deserializer_v1(data, callback); } else { deserializer = new ns.backward.Deserializer_v0(data, callback); @@ -47,110 +22,70 @@ }; ns.Deserializer.prototype.deserialize = function () { - var i; - var j; - var buffer = this.data_; - var arr8 = new Uint8Array(buffer); - var arr16 = new Uint16Array(arr8.buffer); - var sub; + var data = this.data_; + var piskelData = data.piskel; + var name = piskelData.name || 'Deserialized piskel'; + var description = piskelData.description || ''; - /********/ - /* META */ - /********/ - // Piskel meta - var modelVersion = arr16[0]; - var width = arr16[1]; - var height = arr16[2]; - var fps = arr16[3]; + var descriptor = new pskl.model.piskel.Descriptor(name, description); + this.piskel_ = new pskl.model.Piskel(piskelData.width, piskelData.height, descriptor); - // Descriptor meta - var descriptorNameLength = arr16[4]; - var descriptorDescriptionLength = arr16[5]; - - // Layers meta - var layerCount = arr16[6]; - - /********/ - /* DATA */ - /********/ - // Descriptor name - var descriptorName = ''; - for (i = 0; i < descriptorNameLength; i++) { - descriptorName += String.fromCharCode(arr16[7 + i]); + this.layersToLoad_ = piskelData.layers.length; + if (piskelData.expanded) { + piskelData.layers.forEach(this.loadExpandedLayer.bind(this)); + } else { + piskelData.layers.forEach(this.deserializeLayer.bind(this)); } + }; - // Descriptor description - var descriptorDescription = ''; - for (i = 0; i < descriptorDescriptionLength; i++) { - descriptorDescription = String.fromCharCode(arr16[7 + descriptorNameLength + i]); - } + ns.Deserializer.prototype.deserializeLayer = function (layerString, index) { + var layerData = JSON.parse(layerString); + var layer = new pskl.model.Layer(layerData.name); + layer.setOpacity(layerData.opacity); - // Layers - var layerStartIndex = 7 + descriptorNameLength + descriptorDescriptionLength; - var layers = []; - var layer; - for (i = 0; i < layerCount; i++) { - layer = {}; - var frames = []; + // 1 - create an image to load the base64PNG representing the layer + var base64PNG = layerData.base64PNG; + var image = new Image(); - // Meta - var layerNameLength = arr16[layerStartIndex]; - var opacity = arr16[layerStartIndex + 1] / 65535; - var frameCount = arr16[layerStartIndex + 2]; - var dataUriLengthFirstHalf = arr16[layerStartIndex + 3]; - var dataUriLengthSecondHalf = arr16[layerStartIndex + 4]; - var dataUriLength = (dataUriLengthSecondHalf >>> 0) | (dataUriLengthFirstHalf << 16 >>> 0); + // 2 - attach the onload callback that will be triggered asynchronously + image.onload = function () { + // 5 - extract the frames from the loaded image + var frames = pskl.utils.LayerUtils.createFramesFromSpritesheet(image, layerData.frameCount); + // 6 - add each image to the layer + this.addFramesToLayer(frames, layer, index); + }.bind(this); - // Name - var layerName = ''; - for (j = 0; j < layerNameLength; j++) { - layerName += String.fromCharCode(arr16[layerStartIndex + 5 + j]); - } + // 3 - set the source of the image + image.src = base64PNG; + return layer; + }; - // Data URI - var dataUri = ''; - for (j = 0; j < dataUriLength; j++) { - dataUri += String.fromCharCode(arr8[(layerStartIndex + 5 + layerNameLength) * 2 + j]); - } - dataUri = 'data:image/png;base64,' + dataUri; + ns.Deserializer.prototype.loadExpandedLayer = function (layerData, index) { + var width = this.piskel_.getWidth(); + var height = this.piskel_.getHeight(); + var layer = new pskl.model.Layer(layerData.name); + layer.setOpacity(layerData.opacity); + var frames = layerData.grids.map(function (grid) { + return pskl.model.Frame.fromPixelGrid(grid, width, height); + }); + this.addFramesToLayer(frames, layer, index); + return layer; + }; - layerStartIndex += Math.ceil(5 + layerNameLength + (dataUriLength / 2)); + ns.Deserializer.prototype.addFramesToLayer = function (frames, layer, index) { + frames.forEach(layer.addFrame.bind(layer)); - layer.name = layerName; - layer.opacity = opacity; - layer.frameCount = frameCount; - layer.dataUri = dataUri; - layers.push(layer); - } + this.layers_[index] = layer; + this.onLayerLoaded_(); + }; - var descriptor = new pskl.model.piskel.Descriptor(descriptorName, descriptorDescription); - var piskel = new pskl.model.Piskel(width, height, descriptor); - var loadedLayers = 0; - - var loadLayerImage = function(layer, cb) { - var image = new Image(); - image.onload = function() { - var frames = pskl.utils.LayerUtils.createFramesFromSpritesheet(this, layer.frameCount); - frames.forEach(function (frame) { - layer.model.addFrame(frame); - }); - - loadedLayers++; - if (loadedLayers == layerCount) { - cb(piskel, {fps: fps}); - } - }; - image.src = layer.dataUri; - }; - - for (i = 0; i < layerCount; i++) { - layer = layers[i]; - var nlayer = new pskl.model.Layer(layer.name); - layer.model = nlayer; - nlayer.setOpacity(layer.opacity); - piskel.addLayer(nlayer); - - loadLayerImage.bind(this, layer, this.callback_)(); + ns.Deserializer.prototype.onLayerLoaded_ = function () { + this.layersToLoad_ = this.layersToLoad_ - 1; + if (this.layersToLoad_ === 0) { + this.layers_.forEach(function (layer) { + this.piskel_.addLayer(layer); + }.bind(this)); + this.callback_(this.piskel_, {fps: this.data_.piskel.fps}); } }; })(); diff --git a/src/js/utils/serialization/Serializer.js b/src/js/utils/serialization/Serializer.js index 397ce4c4..1e9509e4 100644 --- a/src/js/utils/serialization/Serializer.js +++ b/src/js/utils/serialization/Serializer.js @@ -1,175 +1,34 @@ (function () { - var ns = $.namespace('pskl.utils'); - - /** - ********* - * META * - ********* - * // Piskel - * [0] = model version - * [1] = width - * [2] = height - * [3] = fps - * - * // Descriptor - * [4] = name length - * [5] = description length - * - * // Layers - * [6] = layers count - * [layer data index start] = layer name length - * [layer data index start + 1] = opacity - * [layer data index start + 2] = frame count - * [layer data index start + 3] = base 64 png data url length (upper 16 bits) - * [layer data index start + 4] = base 64 png data url length (lower 16 bits) - * - ********* - * DATA * - ********* - * [7..name length-1] = name - * [name length..description length-1] = description - * [layer data index start + 4..layer name length-1] = layer name - * [layer name length..base 64 png data url length-1] = base 64 png data url - * - */ + var ns = $.namespace('pskl.utils.serialization'); ns.Serializer = { - calculateRequiredBytes : function(piskel, framesData) { - var width = piskel.getWidth(); - var height = piskel.getHeight(); - var descriptorNameLength = piskel.getDescriptor().name.length; - var descriptorDescriptionLength = piskel.getDescriptor().description.length; - var layersLength = piskel.getLayers().length; - - var bytes = 0; - - /********/ - /* META */ - /********/ - // Piskel meta - bytes += 4 * 2; - - // Descriptor meta - bytes += 2 * 2; - - // Layers meta - bytes += 1 * 2; - - /********/ - /* DATA */ - /********/ - // Descriptor name - bytes += descriptorNameLength * 2; - - // Descriptor description - bytes += descriptorDescriptionLength * 2; - - // Layers - for (var i = 0, layers = piskel.getLayers(); i < layers.length; i++) { - bytes += 5 * 2; - bytes += layers[i].name.length * 2; - bytes += framesData[i].length; - if (bytes % 2 == 1) { - bytes++; + serialize : function (piskel) { + var serializedLayers = piskel.getLayers().map(function (l) { + return pskl.utils.serialization.Serializer.serializeLayer(l); + }); + return JSON.stringify({ + modelVersion : Constants.MODEL_VERSION, + piskel : { + name : piskel.getDescriptor().name, + description : piskel.getDescriptor().description, + fps : pskl.app.piskelController.getFPS(), + height : piskel.getHeight(), + width : piskel.getWidth(), + layers : serializedLayers } - } - - return bytes; + }); }, - serializePiskel : function (piskel, expanded) { - var i; - var j; - var layers; - var dataUri; - var dataUriLength; - - // Render frames - var framesData = []; - for (i = 0, layers = piskel.getLayers(); i < layers.length; i++) { - var renderer = new pskl.rendering.FramesheetRenderer(layers[i].getFrames()); - dataUri = renderer.renderAsCanvas().toDataURL().split(',')[1]; - dataUriLength = dataUri.length; - framesData.push({uri: dataUri, length: dataUriLength}); - } - - var bytes = pskl.utils.Serializer.calculateRequiredBytes(piskel, framesData); - - var buffer = new ArrayBuffer(bytes); - var arr8 = new Uint8Array(buffer); - var arr16 = new Uint16Array(buffer); - - var width = piskel.getWidth(); - var height = piskel.getHeight(); - var descriptorName = piskel.getDescriptor().name; - var descriptorNameLength = descriptorName.length; - var descriptorDescription = piskel.getDescriptor().description; - var descriptorDescriptionLength = descriptorDescription.length; - - /********/ - /* META */ - /********/ - // Piskel meta - arr16[0] = Constants.MODEL_VERSION; - arr16[1] = width; - arr16[2] = height; - arr16[3] = pskl.app.piskelController.getFPS(); - - // Descriptor meta - arr16[4] = descriptorNameLength; - arr16[5] = descriptorDescriptionLength; - - // Layers meta - arr16[6] = piskel.getLayers().length; - - /********/ - /* DATA */ - /********/ - // Descriptor name - for (i = 0; i < descriptorNameLength; i++) { - arr16[7 + i] = descriptorName.charCodeAt(i); - } - - // Descriptor description - for (i = 0; i < descriptorDescriptionLength; i++) { - arr16[7 + descriptorNameLength + i] = descriptorDescription.charCodeAt(i); - } - - // Layers - var layerStartIndex = 7 + descriptorNameLength + descriptorDescriptionLength; - for (i = 0, layers = piskel.getLayers(); i < layers.length; i++) { - var layer = layers[i]; - var frames = layer.getFrames(); - - var layerName = layer.getName(); - var layerNameLength = layerName.length; - var opacity = layer.getOpacity(); - var frameCount = frames.length; - - dataUri = framesData[i].uri; - dataUriLength = framesData[i].length; - - // Meta - arr16[layerStartIndex] = layerNameLength; - arr16[layerStartIndex + 1] = Math.floor(opacity * 65535); - arr16[layerStartIndex + 2] = frameCount; - arr16[layerStartIndex + 3] = ((dataUriLength & 0xffff0000) >> 16) >>> 0; // Upper 16 - arr16[layerStartIndex + 4] = ((dataUriLength & 0x0000ffff)) >>> 0; // Lower 16 - - // Name - for (j = 0; j < layerNameLength; j++) { - arr16[layerStartIndex + 5 + j] = layerName.charCodeAt(j); - } - - // Data URI - for (j = 0; j < dataUriLength; j++) { - arr8[(layerStartIndex + 5 + layerNameLength) * 2 + j] = dataUri.charCodeAt(j); - } - - layerStartIndex += Math.ceil(5 + layerNameLength + (dataUriLength / 2)); - } - - return buffer; + serializeLayer : function (layer) { + var frames = layer.getFrames(); + var layerToSerialize = { + name : layer.getName(), + opacity : layer.getOpacity(), + frameCount : frames.length + }; + var renderer = new pskl.rendering.FramesheetRenderer(frames); + layerToSerialize.base64PNG = renderer.renderAsCanvas().toDataURL(); + return JSON.stringify(layerToSerialize); } }; })(); diff --git a/src/js/utils/serialization/StringSerializer.js b/src/js/utils/serialization/StringSerializer.js deleted file mode 100644 index 4fee4d75..00000000 --- a/src/js/utils/serialization/StringSerializer.js +++ /dev/null @@ -1,34 +0,0 @@ -(function () { - var ns = $.namespace('pskl.utils'); - - ns.StringSerializer = { - serializePiskel : function (piskel) { - var serializedLayers = piskel.getLayers().map(function (l) { - return pskl.utils.StringSerializer.serializeLayer(l); - }); - return JSON.stringify({ - modelVersion : 2, - piskel : { - name : piskel.getDescriptor().name, - description : piskel.getDescriptor().description, - fps : pskl.app.piskelController.getFPS(), - height : piskel.getHeight(), - width : piskel.getWidth(), - layers : serializedLayers - } - }); - }, - - serializeLayer : function (layer) { - var frames = layer.getFrames(); - var layerToSerialize = { - name : layer.getName(), - opacity : layer.getOpacity(), - frameCount : frames.length - }; - var renderer = new pskl.rendering.FramesheetRenderer(frames); - layerToSerialize.base64PNG = renderer.renderAsCanvas().toDataURL(); - return JSON.stringify(layerToSerialize); - } - }; -})(); diff --git a/src/js/utils/serialization/arraybuffer/ArrayBufferDeserializer.js b/src/js/utils/serialization/arraybuffer/ArrayBufferDeserializer.js new file mode 100644 index 00000000..1ada4e10 --- /dev/null +++ b/src/js/utils/serialization/arraybuffer/ArrayBufferDeserializer.js @@ -0,0 +1,113 @@ +(function () { + var ns = $.namespace('pskl.utils.serialization.arraybuffer'); + + ns.ArrayBufferDeserializer = { + deserialize : function (data, callback) { + var i; + var j; + var buffer = data; + var arr8 = new Uint8Array(buffer); + var arr16 = new Uint16Array(arr8.buffer); + var sub; + + /********/ + /* META */ + /********/ + // Piskel meta + var modelVersion = arr16[0]; + var width = arr16[1]; + var height = arr16[2]; + var fps = arr16[3]; + + // Descriptor meta + var descriptorNameLength = arr16[4]; + var descriptorDescriptionLength = arr16[5]; + + // Layers meta + var layerCount = arr16[6]; + + /********/ + /* DATA */ + /********/ + // Descriptor name + var descriptorName = ''; + for (i = 0; i < descriptorNameLength; i++) { + descriptorName += String.fromCharCode(arr16[7 + i]); + } + + // Descriptor description + var descriptorDescription = ''; + for (i = 0; i < descriptorDescriptionLength; i++) { + descriptorDescription = String.fromCharCode(arr16[7 + descriptorNameLength + i]); + } + + // Layers + var layerStartIndex = 7 + descriptorNameLength + descriptorDescriptionLength; + var layers = []; + var layer; + for (i = 0; i < layerCount; i++) { + layer = {}; + var frames = []; + + // Meta + var layerNameLength = arr16[layerStartIndex]; + var opacity = arr16[layerStartIndex + 1] / 65535; + var frameCount = arr16[layerStartIndex + 2]; + var dataUriLengthFirstHalf = arr16[layerStartIndex + 3]; + var dataUriLengthSecondHalf = arr16[layerStartIndex + 4]; + var dataUriLength = (dataUriLengthSecondHalf >>> 0) | (dataUriLengthFirstHalf << 16 >>> 0); + + // Name + var layerName = ''; + for (j = 0; j < layerNameLength; j++) { + layerName += String.fromCharCode(arr16[layerStartIndex + 5 + j]); + } + + // Data URI + var dataUri = ''; + for (j = 0; j < dataUriLength; j++) { + dataUri += String.fromCharCode(arr8[(layerStartIndex + 5 + layerNameLength) * 2 + j]); + } + dataUri = 'data:image/png;base64,' + dataUri; + + layerStartIndex += Math.ceil(5 + layerNameLength + (dataUriLength / 2)); + + layer.name = layerName; + layer.opacity = opacity; + layer.frameCount = frameCount; + layer.dataUri = dataUri; + layers.push(layer); + } + + var descriptor = new pskl.model.piskel.Descriptor(descriptorName, descriptorDescription); + var piskel = new pskl.model.Piskel(width, height, descriptor); + var loadedLayers = 0; + + var loadLayerImage = function(layer, cb) { + var image = new Image(); + image.onload = function() { + var frames = pskl.utils.LayerUtils.createFramesFromSpritesheet(this, layer.frameCount); + frames.forEach(function (frame) { + layer.model.addFrame(frame); + }); + + loadedLayers++; + if (loadedLayers == layerCount) { + cb(piskel, {fps: fps}); + } + }; + image.src = layer.dataUri; + }; + + for (i = 0; i < layerCount; i++) { + layer = layers[i]; + var nlayer = new pskl.model.Layer(layer.name); + layer.model = nlayer; + nlayer.setOpacity(layer.opacity); + piskel.addLayer(nlayer); + + loadLayerImage.bind(this, layer, callback)(); + } + } + }; +})(); diff --git a/src/js/utils/serialization/arraybuffer/ArrayBufferSerializer.js b/src/js/utils/serialization/arraybuffer/ArrayBufferSerializer.js new file mode 100644 index 00000000..8e345a92 --- /dev/null +++ b/src/js/utils/serialization/arraybuffer/ArrayBufferSerializer.js @@ -0,0 +1,175 @@ +(function () { + var ns = $.namespace('pskl.utils.serialization.arraybuffer'); + + /** + ********* + * META * + ********* + * // Piskel + * [0] = model version + * [1] = width + * [2] = height + * [3] = fps + * + * // Descriptor + * [4] = name length + * [5] = description length + * + * // Layers + * [6] = layers count + * [layer data index start] = layer name length + * [layer data index start + 1] = opacity + * [layer data index start + 2] = frame count + * [layer data index start + 3] = base 64 png data url length (upper 16 bits) + * [layer data index start + 4] = base 64 png data url length (lower 16 bits) + * + ********* + * DATA * + ********* + * [7..name length-1] = name + * [name length..description length-1] = description + * [layer data index start + 4..layer name length-1] = layer name + * [layer name length..base 64 png data url length-1] = base 64 png data url + * + */ + + ns.ArrayBufferSerializer = { + calculateRequiredBytes : function(piskel, framesData) { + var width = piskel.getWidth(); + var height = piskel.getHeight(); + var descriptorNameLength = piskel.getDescriptor().name.length; + var descriptorDescriptionLength = piskel.getDescriptor().description.length; + var layersLength = piskel.getLayers().length; + + var bytes = 0; + + /********/ + /* META */ + /********/ + // Piskel meta + bytes += 4 * 2; + + // Descriptor meta + bytes += 2 * 2; + + // Layers meta + bytes += 1 * 2; + + /********/ + /* DATA */ + /********/ + // Descriptor name + bytes += descriptorNameLength * 2; + + // Descriptor description + bytes += descriptorDescriptionLength * 2; + + // Layers + for (var i = 0, layers = piskel.getLayers(); i < layers.length; i++) { + bytes += 5 * 2; + bytes += layers[i].name.length * 2; + bytes += framesData[i].length; + if (bytes % 2 == 1) { + bytes++; + } + } + + return bytes; + }, + + serialize : function (piskel, expanded) { + var i; + var j; + var layers; + var dataUri; + var dataUriLength; + + // Render frames + var framesData = []; + for (i = 0, layers = piskel.getLayers(); i < layers.length; i++) { + var renderer = new pskl.rendering.FramesheetRenderer(layers[i].getFrames()); + dataUri = renderer.renderAsCanvas().toDataURL().split(',')[1]; + dataUriLength = dataUri.length; + framesData.push({uri: dataUri, length: dataUriLength}); + } + + var bytes = ns.ArrayBufferSerializer.calculateRequiredBytes(piskel, framesData); + + var buffer = new ArrayBuffer(bytes); + var arr8 = new Uint8Array(buffer); + var arr16 = new Uint16Array(buffer); + + var width = piskel.getWidth(); + var height = piskel.getHeight(); + var descriptorName = piskel.getDescriptor().name; + var descriptorNameLength = descriptorName.length; + var descriptorDescription = piskel.getDescriptor().description; + var descriptorDescriptionLength = descriptorDescription.length; + + /********/ + /* META */ + /********/ + // Piskel meta + arr16[0] = Constants.MODEL_VERSION; + arr16[1] = width; + arr16[2] = height; + arr16[3] = pskl.app.piskelController.getFPS(); + + // Descriptor meta + arr16[4] = descriptorNameLength; + arr16[5] = descriptorDescriptionLength; + + // Layers meta + arr16[6] = piskel.getLayers().length; + + /********/ + /* DATA */ + /********/ + // Descriptor name + for (i = 0; i < descriptorNameLength; i++) { + arr16[7 + i] = descriptorName.charCodeAt(i); + } + + // Descriptor description + for (i = 0; i < descriptorDescriptionLength; i++) { + arr16[7 + descriptorNameLength + i] = descriptorDescription.charCodeAt(i); + } + + // Layers + var layerStartIndex = 7 + descriptorNameLength + descriptorDescriptionLength; + for (i = 0, layers = piskel.getLayers(); i < layers.length; i++) { + var layer = layers[i]; + var frames = layer.getFrames(); + + var layerName = layer.getName(); + var layerNameLength = layerName.length; + var opacity = layer.getOpacity(); + var frameCount = frames.length; + + dataUri = framesData[i].uri; + dataUriLength = framesData[i].length; + + // Meta + arr16[layerStartIndex] = layerNameLength; + arr16[layerStartIndex + 1] = Math.floor(opacity * 65535); + arr16[layerStartIndex + 2] = frameCount; + arr16[layerStartIndex + 3] = ((dataUriLength & 0xffff0000) >> 16) >>> 0; // Upper 16 + arr16[layerStartIndex + 4] = ((dataUriLength & 0x0000ffff)) >>> 0; // Lower 16 + + // Name + for (j = 0; j < layerNameLength; j++) { + arr16[layerStartIndex + 5 + j] = layerName.charCodeAt(j); + } + + // Data URI + for (j = 0; j < dataUriLength; j++) { + arr8[(layerStartIndex + 5 + layerNameLength) * 2 + j] = dataUri.charCodeAt(j); + } + + layerStartIndex += Math.ceil(5 + layerNameLength + (dataUriLength / 2)); + } + + return buffer; + } + }; +})(); diff --git a/src/js/utils/serialization/backward/Deserializer_v2.js b/src/js/utils/serialization/backward/Deserializer_v2.js deleted file mode 100644 index 70064e2c..00000000 --- a/src/js/utils/serialization/backward/Deserializer_v2.js +++ /dev/null @@ -1,79 +0,0 @@ -(function () { - var ns = $.namespace('pskl.utils.serialization.backward'); - - ns.Deserializer_v2 = function (data, callback) { - this.layersToLoad_ = 0; - this.data_ = data; - this.callback_ = callback; - this.piskel_ = null; - this.layers_ = []; - }; - - ns.Deserializer_v2.prototype.deserialize = function () { - var data = this.data_; - var piskelData = data.piskel; - var name = piskelData.name || 'Deserialized piskel'; - var description = piskelData.description || ''; - - var descriptor = new pskl.model.piskel.Descriptor(name, description); - this.piskel_ = new pskl.model.Piskel(piskelData.width, piskelData.height, descriptor); - - this.layersToLoad_ = piskelData.layers.length; - if (piskelData.expanded) { - piskelData.layers.forEach(this.loadExpandedLayer.bind(this)); - } else { - piskelData.layers.forEach(this.deserializeLayer.bind(this)); - } - }; - - ns.Deserializer_v2.prototype.deserializeLayer = function (layerString, index) { - var layerData = JSON.parse(layerString); - var layer = new pskl.model.Layer(layerData.name); - layer.setOpacity(layerData.opacity); - - // 1 - create an image to load the base64PNG representing the layer - var base64PNG = layerData.base64PNG; - var image = new Image(); - - // 2 - attach the onload callback that will be triggered asynchronously - image.onload = function () { - // 5 - extract the frames from the loaded image - var frames = pskl.utils.LayerUtils.createFramesFromSpritesheet(image, layerData.frameCount); - // 6 - add each image to the layer - this.addFramesToLayer(frames, layer, index); - }.bind(this); - - // 3 - set the source of the image - image.src = base64PNG; - return layer; - }; - - ns.Deserializer_v2.prototype.loadExpandedLayer = function (layerData, index) { - var width = this.piskel_.getWidth(); - var height = this.piskel_.getHeight(); - var layer = new pskl.model.Layer(layerData.name); - layer.setOpacity(layerData.opacity); - var frames = layerData.grids.map(function (grid) { - return pskl.model.Frame.fromPixelGrid(grid, width, height); - }); - this.addFramesToLayer(frames, layer, index); - return layer; - }; - - ns.Deserializer_v2.prototype.addFramesToLayer = function (frames, layer, index) { - frames.forEach(layer.addFrame.bind(layer)); - - this.layers_[index] = layer; - this.onLayerLoaded_(); - }; - - ns.Deserializer_v2.prototype.onLayerLoaded_ = function () { - this.layersToLoad_ = this.layersToLoad_ - 1; - if (this.layersToLoad_ === 0) { - this.layers_.forEach(function (layer) { - this.piskel_.addLayer(layer); - }.bind(this)); - this.callback_(this.piskel_, {fps: this.data_.piskel.fps}); - } - }; -})(); diff --git a/src/piskel-script-list.js b/src/piskel-script-list.js index 62ce9369..e5d6f605 100644 --- a/src/piskel-script-list.js +++ b/src/piskel-script-list.js @@ -40,11 +40,11 @@ "js/utils/WorkerUtils.js", "js/utils/Xhr.js", "js/utils/serialization/Serializer.js", - "js/utils/serialization/StringSerializer.js", "js/utils/serialization/Deserializer.js", + "js/utils/serialization/arraybuffer/ArrayBufferDeserializer.js", + "js/utils/serialization/arraybuffer/ArrayBufferSerializer.js", "js/utils/serialization/backward/Deserializer_v0.js", "js/utils/serialization/backward/Deserializer_v1.js", - "js/utils/serialization/backward/Deserializer_v2.js", // GIF Encoding libraries "js/lib/gif/gif.worker.js", diff --git a/test/js/service/HistoryServiceTest.js b/test/js/service/HistoryServiceTest.js index 8d2719ce..4e8c6aac 100644 --- a/test/js/service/HistoryServiceTest.js +++ b/test/js/service/HistoryServiceTest.js @@ -22,16 +22,15 @@ describe("History Service suite", function() { }; var createMockHistoryService = function () { - var mockPiskelController = { - serialize : function () { - return SERIALIZED_PISKEL; - } - }; + var mockPiskelController = { getPiskel : function () {} }; var mockShortcutService = { registerShortcuts : function () {}, registerShortcut : function () {} }; - return new pskl.service.HistoryService(mockPiskelController, mockShortcutService); + return new pskl.service.HistoryService(mockPiskelController, mockShortcutService, + { deserialize : function () {}}, + { serialize : function () { return SERIALIZED_PISKEL }} + ); }; it("starts at -1", function() { diff --git a/test/js/utils/serialization/SerializerTest.js b/test/js/utils/serialization/SerializerTest.js index c6508817..44d21bf5 100644 --- a/test/js/utils/serialization/SerializerTest.js +++ b/test/js/utils/serialization/SerializerTest.js @@ -28,10 +28,10 @@ describe("Serialization/Deserialization test", function() { layer.addFrame(frame); }); - var serializedPiskel = pskl.utils.Serializer.serializePiskel(piskel); + var serializedPiskel = pskl.utils.serialization.Serializer.serialize(piskel); var deserializer = pskl.utils.serialization.Deserializer; - deserializer.deserialize(serializedPiskel, function (p) { + deserializer.deserialize(JSON.parse(serializedPiskel), function (p) { expect(p.getLayerAt(0).getOpacity()).toBe(0); expect(p.getLayerAt(1).getOpacity()).toBe(0.3); expect(p.getLayerAt(2).getOpacity()).toBe(0.9);