support chunked layerData in regular deserializer
This commit is contained in:
parent
dba62d2b0d
commit
66c941dd25
9 changed files with 107 additions and 132 deletions
|
@ -90,13 +90,15 @@ module.exports = function(grunt) {
|
||||||
browser : true,
|
browser : true,
|
||||||
trailing : true,
|
trailing : true,
|
||||||
curly : true,
|
curly : true,
|
||||||
globals : {'$':true, 'jQuery' : true, 'pskl':true, 'Events':true, 'Constants':true, 'console' : true, 'module':true, 'require':true, 'Q':true}
|
globals : {'$':true, 'jQuery' : true, 'pskl':true, 'Events':true, 'Constants':true, 'console' : true, 'module':true, 'require':true, 'Q':true, 'Promise': true}
|
||||||
},
|
},
|
||||||
files: [
|
files: [
|
||||||
|
// Includes
|
||||||
'Gruntfile.js',
|
'Gruntfile.js',
|
||||||
'package.json',
|
'package.json',
|
||||||
'src/js/**/*.js',
|
'src/js/**/*.js',
|
||||||
'!src/js/**/lib/**/*.js' // Exclude lib folder (note the leading !)
|
// Excludes
|
||||||
|
'!src/js/**/lib/**/*.js'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ module.exports = function(config) {
|
||||||
|
|
||||||
// Polyfill for Object.assign (missing in PhantomJS)
|
// Polyfill for Object.assign (missing in PhantomJS)
|
||||||
piskelScripts.push('./node_modules/phantomjs-polyfill-object-assign/object-assign-polyfill.js');
|
piskelScripts.push('./node_modules/phantomjs-polyfill-object-assign/object-assign-polyfill.js');
|
||||||
|
|
||||||
config.set({
|
config.set({
|
||||||
|
|
||||||
// base path that will be used to resolve all patterns (eg. files, exclude)
|
// base path that will be used to resolve all patterns (eg. files, exclude)
|
||||||
|
@ -24,7 +24,9 @@ module.exports = function(config) {
|
||||||
|
|
||||||
|
|
||||||
// list of files / patterns to load in the browser
|
// list of files / patterns to load in the browser
|
||||||
files: piskelScripts,
|
files: piskelScripts.concat([
|
||||||
|
'./node_modules/promise-polyfill/promise.js'
|
||||||
|
]),
|
||||||
|
|
||||||
|
|
||||||
// list of files to exclude
|
// list of files to exclude
|
||||||
|
|
|
@ -52,7 +52,8 @@
|
||||||
"karma-phantomjs-launcher": "0.2.3",
|
"karma-phantomjs-launcher": "0.2.3",
|
||||||
"load-grunt-tasks": "3.5.0",
|
"load-grunt-tasks": "3.5.0",
|
||||||
"phantomjs": "2.1.7",
|
"phantomjs": "2.1.7",
|
||||||
"phantomjs-polyfill-object-assign": "0.0.2"
|
"phantomjs-polyfill-object-assign": "0.0.2",
|
||||||
|
"promise-polyfill": "6.0.2"
|
||||||
},
|
},
|
||||||
"window": {
|
"window": {
|
||||||
"title": "Piskel",
|
"title": "Piskel",
|
||||||
|
|
|
@ -177,74 +177,60 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alpha compositing using porter duff algorithm :
|
* Create a Frame array from an Image object.
|
||||||
* http://en.wikipedia.org/wiki/Alpha_compositing
|
* Transparent pixels will either be converted to completely opaque or completely transparent pixels.
|
||||||
* http://keithp.com/~keithp/porterduff/p253-porter.pdf
|
*
|
||||||
* @param {String} strColor1 color over
|
* @param {Image} image source image
|
||||||
* @param {String} strColor2 color under
|
* @param {Number} frameCount number of frames in the spritesheet
|
||||||
* @return {String} the composite color
|
* @return {Array<Frame>}
|
||||||
*/
|
*/
|
||||||
mergePixels__ : function (strColor1, strColor2, globalOpacity1) {
|
createFramesFromSpritesheet : function (image, frameCount) {
|
||||||
var col1 = pskl.utils.FrameUtils.toRgba__(strColor1);
|
var layout = [];
|
||||||
var col2 = pskl.utils.FrameUtils.toRgba__(strColor2);
|
for (var i = 0 ; i < frameCount ; i++) {
|
||||||
if (typeof globalOpacity1 == 'number') {
|
layout.push([i]);
|
||||||
col1 = JSON.parse(JSON.stringify(col1));
|
|
||||||
col1.a = globalOpacity1 * col1.a;
|
|
||||||
}
|
}
|
||||||
var a = col1.a + col2.a * (1 - col1.a);
|
var chunkFrames = pskl.utils.FrameUtils.createFramesFromChunk(image, layout);
|
||||||
|
return chunkFrames.map(function (chunkFrame) {
|
||||||
var r = ((col1.r * col1.a + col2.r * col2.a * (1 - col1.a)) / a) | 0;
|
return chunkFrame.frame;
|
||||||
var g = ((col1.g * col1.a + col2.g * col2.a * (1 - col1.a)) / a) | 0;
|
});
|
||||||
var b = ((col1.b * col1.a + col2.b * col2.a * (1 - col1.a)) / a) | 0;
|
|
||||||
|
|
||||||
return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a color defined as a string (hex, rgba, rgb, 'TRANSPARENT') to an Object with r,g,b,a properties.
|
* Create a Frame array from an Image object.
|
||||||
* r, g and b are integers between 0 and 255, a is a float between 0 and 1
|
* Transparent pixels will either be converted to completely opaque or completely transparent pixels.
|
||||||
* @param {String} c color as a string
|
*
|
||||||
* @return {Object} {r:Number,g:Number,b:Number,a:Number}
|
* @param {Image} image source image
|
||||||
|
* @param {Array <Array>} layout description of the frame indexes expected to be found in the chunk
|
||||||
|
* @return {Array<Object>} array of objects containing: {index: frame index, frame: frame instance}
|
||||||
*/
|
*/
|
||||||
toRgba__ : function (c) {
|
createFramesFromChunk : function (image, layout) {
|
||||||
if (colorCache[c]) {
|
var width = image.width;
|
||||||
return colorCache[c];
|
var height = image.height;
|
||||||
|
|
||||||
|
// Recalculate the expected frame dimensions from the layout information
|
||||||
|
var frameWidth = width / layout.length;
|
||||||
|
var frameHeight = height / layout[0].length;
|
||||||
|
|
||||||
|
// Create a canvas adapted to the image size
|
||||||
|
var canvas = pskl.utils.CanvasUtils.createCanvas(frameWidth, frameHeight);
|
||||||
|
var context = canvas.getContext('2d');
|
||||||
|
|
||||||
|
// Draw the zoomed-up pixels to a different canvas context
|
||||||
|
var chunkFrames = [];
|
||||||
|
for (var i = 0 ; i < layout.length ; i++) {
|
||||||
|
var row = layout[i];
|
||||||
|
for (var j = 0 ; j < row.length ; j++) {
|
||||||
|
context.clearRect(0, 0 , frameWidth, frameHeight);
|
||||||
|
context.drawImage(image, frameWidth * i, frameHeight * j, frameWidth, frameHeight, 0, 0, frameWidth, height);
|
||||||
|
var frame = pskl.utils.FrameUtils.createFromCanvas(canvas, 0, 0, frameWidth, height);
|
||||||
|
chunkFrames.push({
|
||||||
|
index : layout[i][j],
|
||||||
|
frame : frame
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var color, matches;
|
|
||||||
if (c === 'TRANSPARENT') {
|
return chunkFrames;
|
||||||
color = {
|
|
||||||
r : 0,
|
|
||||||
g : 0,
|
|
||||||
b : 0,
|
|
||||||
a : 0
|
|
||||||
};
|
|
||||||
} else if (c.indexOf('rgba(') != -1) {
|
|
||||||
matches = /rgba\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(1|0\.\d+)\s*\)/.exec(c);
|
|
||||||
color = {
|
|
||||||
r : parseInt(matches[1], 10),
|
|
||||||
g : parseInt(matches[2], 10),
|
|
||||||
b : parseInt(matches[3], 10),
|
|
||||||
a : parseFloat(matches[4])
|
|
||||||
};
|
|
||||||
} else if (c.indexOf('rgb(') != -1) {
|
|
||||||
matches = /rgb\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/.exec(c);
|
|
||||||
color = {
|
|
||||||
r : parseInt(matches[1], 10),
|
|
||||||
g : parseInt(matches[2], 10),
|
|
||||||
b : parseInt(matches[3], 10),
|
|
||||||
a : 1
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
matches = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(c);
|
|
||||||
color = {
|
|
||||||
r : parseInt(matches[1], 16),
|
|
||||||
g : parseInt(matches[2], 16),
|
|
||||||
b : parseInt(matches[3], 16),
|
|
||||||
a : 1
|
|
||||||
};
|
|
||||||
}
|
|
||||||
colorCache[c] = color;
|
|
||||||
return color;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -2,34 +2,6 @@
|
||||||
var ns = $.namespace('pskl.utils');
|
var ns = $.namespace('pskl.utils');
|
||||||
|
|
||||||
ns.LayerUtils = {
|
ns.LayerUtils = {
|
||||||
/**
|
|
||||||
* Create a Frame array from an Image object.
|
|
||||||
* Transparent pixels will either be converted to completely opaque or completely transparent pixels.
|
|
||||||
* TODO : move to FrameUtils
|
|
||||||
*
|
|
||||||
* @param {Image} image source image
|
|
||||||
* @param {Number} frameCount number of frames in the spritesheet
|
|
||||||
* @return {Array<Frame>}
|
|
||||||
*/
|
|
||||||
createFramesFromSpritesheet : function (image, frameCount) {
|
|
||||||
var width = image.width;
|
|
||||||
var height = image.height;
|
|
||||||
var frameWidth = width / frameCount;
|
|
||||||
|
|
||||||
var canvas = pskl.utils.CanvasUtils.createCanvas(frameWidth, height);
|
|
||||||
var context = canvas.getContext('2d');
|
|
||||||
|
|
||||||
// Draw the zoomed-up pixels to a different canvas context
|
|
||||||
var frames = [];
|
|
||||||
for (var i = 0 ; i < frameCount ; i++) {
|
|
||||||
context.clearRect(0, 0 , frameWidth, height);
|
|
||||||
context.drawImage(image, frameWidth * i, 0, frameWidth, height, 0, 0, frameWidth, height);
|
|
||||||
var frame = pskl.utils.FrameUtils.createFromCanvas(canvas, 0, 0, frameWidth, height);
|
|
||||||
frames.push(frame);
|
|
||||||
}
|
|
||||||
return frames;
|
|
||||||
},
|
|
||||||
|
|
||||||
mergeLayers : function (layerA, layerB) {
|
mergeLayers : function (layerA, layerB) {
|
||||||
var framesA = layerA.getFrames();
|
var framesA = layerA.getFrames();
|
||||||
var framesB = layerB.getFrames();
|
var framesB = layerB.getFrames();
|
||||||
|
|
|
@ -31,11 +31,7 @@
|
||||||
this.piskel_ = new pskl.model.Piskel(piskelData.width, piskelData.height, piskelData.fps, descriptor);
|
this.piskel_ = new pskl.model.Piskel(piskelData.width, piskelData.height, piskelData.fps, descriptor);
|
||||||
|
|
||||||
this.layersToLoad_ = piskelData.layers.length;
|
this.layersToLoad_ = piskelData.layers.length;
|
||||||
if (piskelData.expanded) {
|
piskelData.layers.forEach(this.deserializeLayer.bind(this));
|
||||||
piskelData.layers.forEach(this.loadExpandedLayer.bind(this));
|
|
||||||
} else {
|
|
||||||
piskelData.layers.forEach(this.deserializeLayer.bind(this));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ns.Deserializer.prototype.deserializeLayer = function (layerString, index) {
|
ns.Deserializer.prototype.deserializeLayer = function (layerString, index) {
|
||||||
|
@ -43,42 +39,43 @@
|
||||||
var layer = new pskl.model.Layer(layerData.name);
|
var layer = new pskl.model.Layer(layerData.name);
|
||||||
layer.setOpacity(layerData.opacity);
|
layer.setOpacity(layerData.opacity);
|
||||||
|
|
||||||
// 1 - create an image to load the base64PNG representing the layer
|
// Backward compatibility: if the layerData is not chunked but contains a single base64PNG,
|
||||||
var base64PNG = layerData.base64PNG;
|
// create a fake chunk, expected to represent all frames side-by-side.
|
||||||
var image = new Image();
|
if (typeof layerData.chunks === 'undefined' && layerData.base64PNG) {
|
||||||
|
this.normalizeLayerData_(layerData);
|
||||||
|
}
|
||||||
|
|
||||||
// 2 - attach the onload callback that will be triggered asynchronously
|
var chunks = layerData.chunks;
|
||||||
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
|
// Prepare a frames array to store frame objects extracted from the chunks.
|
||||||
image.src = base64PNG;
|
var frames = [];
|
||||||
return layer;
|
Promise.all(chunks.map(function (chunk) {
|
||||||
};
|
// Create a promise for each chunk.
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
ns.Deserializer.prototype.loadExpandedLayer = function (layerData, index) {
|
var image = new Image();
|
||||||
var width = this.piskel_.getWidth();
|
// Load the chunk image in an Image object.
|
||||||
var height = this.piskel_.getHeight();
|
image.onload = function () {
|
||||||
var layer = new pskl.model.Layer(layerData.name);
|
// extract the chunkFrames from the chunk image
|
||||||
layer.setOpacity(layerData.opacity);
|
var chunkFrames = pskl.utils.FrameUtils.createFramesFromChunk(image, chunk.layout);
|
||||||
var frames = layerData.grids.map(function (grid) {
|
// add each image to the frames array, at the extracted index
|
||||||
return pskl.model.Frame.fromPixelGrid(grid, width, height);
|
chunkFrames.forEach(function (chunkFrame) {
|
||||||
|
frames[chunkFrame.index] = chunkFrame.frame;
|
||||||
|
});
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
image.src = chunk.base64PNG;
|
||||||
|
});
|
||||||
|
})).then(function () {
|
||||||
|
frames.forEach(layer.addFrame.bind(layer));
|
||||||
|
this.layers_[index] = layer;
|
||||||
|
this.onLayerLoaded_();
|
||||||
|
}.bind(this)).catch(function () {
|
||||||
|
console.error('Failed to deserialize layer');
|
||||||
});
|
});
|
||||||
this.addFramesToLayer(frames, layer, index);
|
|
||||||
return layer;
|
return layer;
|
||||||
};
|
};
|
||||||
|
|
||||||
ns.Deserializer.prototype.addFramesToLayer = function (frames, layer, index) {
|
|
||||||
frames.forEach(layer.addFrame.bind(layer));
|
|
||||||
|
|
||||||
this.layers_[index] = layer;
|
|
||||||
this.onLayerLoaded_();
|
|
||||||
};
|
|
||||||
|
|
||||||
ns.Deserializer.prototype.onLayerLoaded_ = function () {
|
ns.Deserializer.prototype.onLayerLoaded_ = function () {
|
||||||
this.layersToLoad_ = this.layersToLoad_ - 1;
|
this.layersToLoad_ = this.layersToLoad_ - 1;
|
||||||
if (this.layersToLoad_ === 0) {
|
if (this.layersToLoad_ === 0) {
|
||||||
|
@ -88,4 +85,19 @@
|
||||||
this.callback_(this.piskel_);
|
this.callback_(this.piskel_);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backward comptibility only. Create a chunk for layerData objects that only contain
|
||||||
|
* an single base64PNG without chunk/layout information.
|
||||||
|
*/
|
||||||
|
ns.Deserializer.prototype.normalizeLayerData_ = function (layerData) {
|
||||||
|
var layout = [];
|
||||||
|
for (var i = 0 ; i < layerData.frameCount ; i++) {
|
||||||
|
layout.push([i]);
|
||||||
|
}
|
||||||
|
layerData.chunks = [{
|
||||||
|
base64PNG : layerData.base64PNG,
|
||||||
|
layout : layout
|
||||||
|
}];
|
||||||
|
};
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -96,7 +96,7 @@
|
||||||
var loadLayerImage = function(layer, cb) {
|
var loadLayerImage = function(layer, cb) {
|
||||||
var image = new Image();
|
var image = new Image();
|
||||||
image.onload = function() {
|
image.onload = function() {
|
||||||
var frames = pskl.utils.LayerUtils.createFramesFromSpritesheet(this, layer.frameCount);
|
var frames = pskl.utils.FrameUtils.createFramesFromSpritesheet(this, layer.frameCount);
|
||||||
frames.forEach(function (frame) {
|
frames.forEach(function (frame) {
|
||||||
layer.model.addFrame(frame);
|
layer.model.addFrame(frame);
|
||||||
});
|
});
|
||||||
|
|
|
@ -85,7 +85,7 @@
|
||||||
return bytes;
|
return bytes;
|
||||||
},
|
},
|
||||||
|
|
||||||
serialize : function (piskel, expanded) {
|
serialize : function (piskel) {
|
||||||
var i;
|
var i;
|
||||||
var j;
|
var j;
|
||||||
var layers;
|
var layers;
|
||||||
|
|
|
@ -86,7 +86,7 @@ describe("FrameUtils suite", function() {
|
||||||
var spritesheet = pskl.utils.FrameUtils.toImage(frame);
|
var spritesheet = pskl.utils.FrameUtils.toImage(frame);
|
||||||
|
|
||||||
// split the spritesheet by 4
|
// split the spritesheet by 4
|
||||||
var frames = pskl.utils.LayerUtils.createFramesFromSpritesheet(spritesheet, 4);
|
var frames = pskl.utils.FrameUtils.createFramesFromSpritesheet(spritesheet, 4);
|
||||||
|
|
||||||
// expect 4 frames of 1x2
|
// expect 4 frames of 1x2
|
||||||
expect(frames.length).toBe(4);
|
expect(frames.length).toBe(4);
|
||||||
|
|
Loading…
Reference in a new issue