mirror of
https://github.com/piskelapp/piskel.git
synced 2023-08-10 21:12:52 +03:00
Cleanup project root
This commit is contained in:
49
src/js/utils/CanvasUtils.js
Normal file
49
src/js/utils/CanvasUtils.js
Normal file
@@ -0,0 +1,49 @@
|
||||
(function () {
|
||||
var ns = $.namespace("pskl");
|
||||
|
||||
ns.CanvasUtils = {
|
||||
createCanvas : function (width, height, classList) {
|
||||
var canvas = document.createElement("canvas");
|
||||
canvas.setAttribute("width", width);
|
||||
canvas.setAttribute("height", height);
|
||||
|
||||
if (typeof classList == "string") {
|
||||
classList = [classList];
|
||||
}
|
||||
if (Array.isArray(classList)) {
|
||||
for (var i = 0 ; i < classList.length ; i++) {
|
||||
canvas.classList.add(classList[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return canvas;
|
||||
},
|
||||
|
||||
/**
|
||||
* By default, all scaling operations on a Canvas 2D Context are performed using antialiasing.
|
||||
* Resizing a 32x32 image to 320x320 will lead to a blurry output.
|
||||
* On Chrome, FF and IE>=11, this can be disabled by setting a property on the Canvas 2D Context.
|
||||
* In this case the browser will use a nearest-neighbor scaling.
|
||||
* @param {Canvas} canvas
|
||||
*/
|
||||
disableImageSmoothing : function (canvas) {
|
||||
var context = canvas.getContext('2d');
|
||||
context.imageSmoothingEnabled = false;
|
||||
context.mozImageSmoothingEnabled = false;
|
||||
context.oImageSmoothingEnabled = false;
|
||||
context.webkitImageSmoothingEnabled = false;
|
||||
context.msImageSmoothingEnabled = false;
|
||||
},
|
||||
|
||||
clear : function (canvas) {
|
||||
if (canvas) {
|
||||
canvas.getContext("2d").clearRect(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
},
|
||||
|
||||
getImageDataFromCanvas : function (canvas) {
|
||||
var sourceContext = canvas.getContext('2d');
|
||||
return sourceContext.getImageData(0, 0, canvas.width, canvas.height).data;
|
||||
}
|
||||
};
|
||||
})();
|
||||
29
src/js/utils/Dom.js
Normal file
29
src/js/utils/Dom.js
Normal file
@@ -0,0 +1,29 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.utils');
|
||||
|
||||
ns.Dom = {
|
||||
/**
|
||||
* Check if a given HTML element is nested inside another
|
||||
* @param {HTMLElement} node Element to test
|
||||
* @param {HTMLElement} parent Potential Ancestor for node
|
||||
* @param {Boolean} excludeParent set to true if the parent should be excluded from potential matches
|
||||
* @return {Boolean} true if parent was found amongst the parentNode chain of node
|
||||
*/
|
||||
isParent : function (node, parent, excludeParent) {
|
||||
if (node && parent) {
|
||||
|
||||
if (excludeParent) {
|
||||
node = node.parentNode;
|
||||
}
|
||||
|
||||
while (node) {
|
||||
if (node === parent) {
|
||||
return true;
|
||||
}
|
||||
node = node.parentNode;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
})();
|
||||
13
src/js/utils/FileUtils.js
Normal file
13
src/js/utils/FileUtils.js
Normal file
@@ -0,0 +1,13 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.utils');
|
||||
|
||||
ns.FileUtils = {
|
||||
readFile : function (file, callback) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(event){
|
||||
callback(event.target.result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
};
|
||||
})();
|
||||
156
src/js/utils/FrameUtils.js
Normal file
156
src/js/utils/FrameUtils.js
Normal file
@@ -0,0 +1,156 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.utils');
|
||||
var colorCache = {};
|
||||
ns.FrameUtils = {
|
||||
merge : function (frames) {
|
||||
var merged = null;
|
||||
if (frames.length) {
|
||||
merged = frames[0].clone();
|
||||
var w = merged.getWidth(), h = merged.getHeight();
|
||||
for (var i = 1 ; i < frames.length ; i++) {
|
||||
pskl.utils.FrameUtils.mergeFrames_(merged, frames[i]);
|
||||
}
|
||||
}
|
||||
return merged;
|
||||
},
|
||||
|
||||
mergeFrames_ : function (frameA, frameB) {
|
||||
frameB.forEachPixel(function (p, col, row) {
|
||||
if (p != Constants.TRANSPARENT_COLOR) {
|
||||
frameA.setPixel(col, row, p);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Alpha compositing using porter duff algorithm :
|
||||
* http://en.wikipedia.org/wiki/Alpha_compositing
|
||||
* http://keithp.com/~keithp/porterduff/p253-porter.pdf
|
||||
* @param {String} strColor1 color over
|
||||
* @param {String} strColor2 color under
|
||||
* @return {String} the composite color
|
||||
*/
|
||||
mergePixels : function (strColor1, strColor2, globalOpacity1) {
|
||||
var col1 = pskl.utils.FrameUtils.toRgba(strColor1);
|
||||
var col2 = pskl.utils.FrameUtils.toRgba(strColor2);
|
||||
if (typeof globalOpacity1 == 'number') {
|
||||
col1 = JSON.parse(JSON.stringify(col1));
|
||||
col1.a = globalOpacity1 * col1.a;
|
||||
}
|
||||
var a = col1.a + col2.a * (1 - col1.a);
|
||||
|
||||
var r = ((col1.r * col1.a + col2.r * col2.a * (1 - col1.a)) / a)|0;
|
||||
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.
|
||||
* r, g and b are integers between 0 and 255, a is a float between 0 and 1
|
||||
* @param {String} c color as a string
|
||||
* @return {Object} {r:Number,g:Number,b:Number,a:Number}
|
||||
*/
|
||||
toRgba : function (c) {
|
||||
if (colorCache[c]) {
|
||||
return colorCache[c];
|
||||
}
|
||||
var color, matches;
|
||||
if (c === 'TRANSPARENT') {
|
||||
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;
|
||||
},
|
||||
|
||||
/*
|
||||
* Create a pskl.model.Frame from an Image object.
|
||||
* Transparent pixels will either be converted to completely opaque or completely transparent pixels.
|
||||
* @param {Image} image source image
|
||||
* @return {pskl.model.Frame} corresponding frame
|
||||
*/
|
||||
createFromImage : function (image) {
|
||||
var w = image.width,
|
||||
h = image.height;
|
||||
var canvas = pskl.CanvasUtils.createCanvas(w, h);
|
||||
var context = canvas.getContext('2d');
|
||||
|
||||
context.drawImage(image, 0,0,w,h,0,0,w,h);
|
||||
var imgData = context.getImageData(0,0,w,h).data;
|
||||
return pskl.utils.FrameUtils.createFromImageData(imgData, w, h);
|
||||
},
|
||||
|
||||
createFromImageData : function (imageData, width, height) {
|
||||
// Draw the zoomed-up pixels to a different canvas context
|
||||
var grid = [];
|
||||
for (var x = 0 ; x < width ; x++){
|
||||
grid[x] = [];
|
||||
for (var y = 0 ; y < height ; y++){
|
||||
// Find the starting index in the one-dimensional image data
|
||||
var i = (y * width + x)*4;
|
||||
var r = imageData[i ];
|
||||
var g = imageData[i+1];
|
||||
var b = imageData[i+2];
|
||||
var a = imageData[i+3];
|
||||
if (a < 125) {
|
||||
grid[x][y] = Constants.TRANSPARENT_COLOR;
|
||||
} else {
|
||||
grid[x][y] = pskl.utils.FrameUtils.rgbToHex(r,g,b);
|
||||
}
|
||||
}
|
||||
}
|
||||
return pskl.model.Frame.fromPixelGrid(grid);
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert a rgb(Number, Number, Number) color to hexadecimal representation
|
||||
* @param {Number} r red value, between 0 and 255
|
||||
* @param {Number} g green value, between 0 and 255
|
||||
* @param {Number} b blue value, between 0 and 255
|
||||
* @return {String} hex representation of the color '#ABCDEF'
|
||||
*/
|
||||
rgbToHex : function (r, g, b) {
|
||||
return "#" + this.componentToHex(r) + this.componentToHex(g) + this.componentToHex(b);
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert a color component (as a Number between 0 and 255) to its string hexa representation
|
||||
* @param {Number} c component value, between 0 and 255
|
||||
* @return {String} eg. '0A'
|
||||
*/
|
||||
componentToHex : function (c) {
|
||||
var hex = c.toString(16);
|
||||
return hex.length == 1 ? "0" + hex : hex;
|
||||
}
|
||||
};
|
||||
})();
|
||||
76
src/js/utils/ImageResizer.js
Normal file
76
src/js/utils/ImageResizer.js
Normal file
@@ -0,0 +1,76 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.utils');
|
||||
|
||||
ns.ImageResizer = {
|
||||
resize : function (image, targetWidth, targetHeight, smoothingEnabled) {
|
||||
var canvas = pskl.CanvasUtils.createCanvas(targetWidth, targetHeight);
|
||||
var context = canvas.getContext('2d');
|
||||
context.save();
|
||||
|
||||
if (!smoothingEnabled) {
|
||||
pskl.CanvasUtils.disableImageSmoothing(canvas);
|
||||
}
|
||||
|
||||
context.translate(canvas.width / 2, canvas.height / 2);
|
||||
context.scale(targetWidth / image.width, targetHeight / image.height);
|
||||
context.drawImage(image, -image.width / 2, -image.height / 2);
|
||||
context.restore();
|
||||
|
||||
return canvas;
|
||||
},
|
||||
|
||||
/**
|
||||
* Manual implementation of resize using a nearest neighbour algorithm
|
||||
* It is slower than relying on the native 'disabledImageSmoothing' available on CanvasRenderingContext2d.
|
||||
* But it can be useful if :
|
||||
* - IE < 11 (doesn't support msDisableImageSmoothing)
|
||||
* - need to display a gap between pixel
|
||||
*
|
||||
* @param {Canvas2d} source original image to be resized, as a 2d canvas
|
||||
* @param {Number} zoom ratio between desired dim / source dim
|
||||
* @param {Number} margin gap to be displayed between pixels
|
||||
* @return {Canvas2d} the resized canvas
|
||||
*/
|
||||
resizeNearestNeighbour : function (source, zoom, margin) {
|
||||
margin = margin || 0;
|
||||
var canvas = pskl.CanvasUtils.createCanvas(zoom*source.width, zoom*source.height);
|
||||
var context = canvas.getContext('2d');
|
||||
|
||||
var imgData = pskl.CanvasUtils.getImageDataFromCanvas(source);
|
||||
|
||||
var yRanges = {},
|
||||
xOffset = 0,
|
||||
yOffset = 0,
|
||||
xRange,
|
||||
yRange;
|
||||
// Draw the zoomed-up pixels to a different canvas context
|
||||
for (var x = 0; x < source.width; x++) {
|
||||
// Calculate X Range
|
||||
xRange = Math.floor((x + 1) * zoom) - xOffset;
|
||||
|
||||
for (var y = 0; y < source.height; y++) {
|
||||
// Calculate Y Range
|
||||
if (!yRanges[y + ""]) {
|
||||
// Cache Y Range
|
||||
yRanges[y + ""] = Math.floor((y + 1) * zoom) - yOffset;
|
||||
}
|
||||
yRange = yRanges[y + ""];
|
||||
|
||||
var i = (y * source.width + x) * 4;
|
||||
var r = imgData[i];
|
||||
var g = imgData[i + 1];
|
||||
var b = imgData[i + 2];
|
||||
var a = imgData[i + 3];
|
||||
|
||||
context.fillStyle = "rgba(" + r + "," + g + "," + b + "," + (a / 255) + ")";
|
||||
context.fillRect(xOffset, yOffset, xRange-margin, yRange-margin);
|
||||
|
||||
yOffset += yRange;
|
||||
}
|
||||
yOffset = 0;
|
||||
xOffset += xRange;
|
||||
}
|
||||
return canvas;
|
||||
}
|
||||
};
|
||||
})();
|
||||
31
src/js/utils/LayerUtils.js
Normal file
31
src/js/utils/LayerUtils.js
Normal file
@@ -0,0 +1,31 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.utils');
|
||||
|
||||
ns.LayerUtils = {
|
||||
/**
|
||||
* Create a pskl.model.Layer from an Image object.
|
||||
* Transparent pixels will either be converted to completely opaque or completely transparent pixels.
|
||||
* @param {Image} image source image
|
||||
* @return {pskl.model.Frame} corresponding frame
|
||||
*/
|
||||
createFromImage : function (image, frameCount) {
|
||||
var w = image.width,
|
||||
h = image.height,
|
||||
frameWidth = w / frameCount;
|
||||
|
||||
var canvas = pskl.CanvasUtils.createCanvas(w, h);
|
||||
var context = canvas.getContext('2d');
|
||||
|
||||
context.drawImage(image, 0,0,w,h,0,0,w,h);
|
||||
// Draw the zoomed-up pixels to a different canvas context
|
||||
var frames = [];
|
||||
for (var i = 0 ; i < frameCount ; i++) {
|
||||
var imgData = context.getImageData(frameWidth*i,0,frameWidth,h).data;
|
||||
var frame = pskl.utils.FrameUtils.createFromImageData(imgData, frameWidth, h);
|
||||
frames.push(frame);
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
9
src/js/utils/Math.js
Normal file
9
src/js/utils/Math.js
Normal file
@@ -0,0 +1,9 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.utils');
|
||||
|
||||
ns.Math = {
|
||||
minmax : function (val, min, max) {
|
||||
return Math.max(Math.min(val, max), min);
|
||||
}
|
||||
};
|
||||
})();
|
||||
177
src/js/utils/PixelUtils.js
Normal file
177
src/js/utils/PixelUtils.js
Normal file
@@ -0,0 +1,177 @@
|
||||
(function () {
|
||||
var ns = $.namespace("pskl");
|
||||
|
||||
ns.PixelUtils = {
|
||||
|
||||
getRectanglePixels : function (x0, y0, x1, y1) {
|
||||
var rectangle = this.getOrderedRectangleCoordinates(x0, y0, x1, y1);
|
||||
var pixels = [];
|
||||
|
||||
for(var x = rectangle.x0; x <= rectangle.x1; x++) {
|
||||
for(var y = rectangle.y0; y <= rectangle.y1; y++) {
|
||||
pixels.push({"col": x, "row": y});
|
||||
}
|
||||
}
|
||||
|
||||
return pixels;
|
||||
},
|
||||
|
||||
getBoundRectanglePixels : function (x0, y0, x1, y1) {
|
||||
var rectangle = this.getOrderedRectangleCoordinates(x0, y0, x1, y1);
|
||||
var pixels = [];
|
||||
// Creating horizontal sides of the rectangle:
|
||||
for(var x = rectangle.x0; x <= rectangle.x1; x++) {
|
||||
pixels.push({"col": x, "row": rectangle.y0});
|
||||
pixels.push({"col": x, "row": rectangle.y1});
|
||||
}
|
||||
|
||||
// Creating vertical sides of the rectangle:
|
||||
for(var y = rectangle.y0; y <= rectangle.y1; y++) {
|
||||
pixels.push({"col": rectangle.x0, "row": y});
|
||||
pixels.push({"col": rectangle.x1, "row": y});
|
||||
}
|
||||
|
||||
return pixels;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return an object of ordered rectangle coordinate.
|
||||
* In returned object {x0, y0} => top left corner - {x1, y1} => bottom right corner
|
||||
* @private
|
||||
*/
|
||||
getOrderedRectangleCoordinates : function (x0, y0, x1, y1) {
|
||||
return {
|
||||
x0 : Math.min(x0, x1),
|
||||
y0 : Math.min(y0, y1),
|
||||
x1 : Math.max(x0, x1),
|
||||
y1 : Math.max(y0, y1)
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the list of pixels that would have been filled by a paintbucket tool applied
|
||||
* on pixel at coordinate (x,y).
|
||||
* This function is not altering the Frame object argument.
|
||||
*
|
||||
* @param frame pskl.model.Frame The frame target in which we want to paintbucket
|
||||
* @param col number Column coordinate in the frame
|
||||
* @param row number Row coordinate in the frame
|
||||
*
|
||||
* @return an array of the pixel coordinates paint with the replacement color
|
||||
*/
|
||||
getSimilarConnectedPixelsFromFrame: function(frame, col, row) {
|
||||
// To get the list of connected (eg the same color) pixels, we will use the paintbucket algorithm
|
||||
// in a fake cloned frame. The returned pixels by the paintbucket algo are the painted pixels
|
||||
// and are as well connected.
|
||||
var fakeFrame = frame.clone(); // We just want to
|
||||
var fakeFillColor = "sdfsdfsdf"; // A fake color that will never match a real color.
|
||||
var paintedPixels = this.paintSimilarConnectedPixelsFromFrame(fakeFrame, col, row, fakeFillColor);
|
||||
|
||||
return paintedPixels;
|
||||
},
|
||||
|
||||
/**
|
||||
* Apply the paintbucket tool in a frame at the (col, row) initial position
|
||||
* with the replacement color.
|
||||
*
|
||||
* @param frame pskl.model.Frame The frame target in which we want to paintbucket
|
||||
* @param col number Column coordinate in the frame
|
||||
* @param row number Row coordinate in the frame
|
||||
* @param replacementColor string Hexadecimal color used to fill the area
|
||||
*
|
||||
* @return an array of the pixel coordinates paint with the replacement color
|
||||
*/
|
||||
paintSimilarConnectedPixelsFromFrame: function(frame, col, row, replacementColor) {
|
||||
/**
|
||||
* Queue linear Flood-fill (node, target-color, replacement-color):
|
||||
* 1. Set Q to the empty queue.
|
||||
* 2. If the color of node is not equal to target-color, return.
|
||||
* 3. Add node to Q.
|
||||
* 4. For each element n of Q:
|
||||
* 5. If the color of n is equal to target-color:
|
||||
* 6. Set w and e equal to n.
|
||||
* 7. Move w to the west until the color of the node to the west of w no longer matches target-color.
|
||||
* 8. Move e to the east until the color of the node to the east of e no longer matches target-color.
|
||||
* 9. Set the color of nodes between w and e to replacement-color.
|
||||
* 10. For each node n between w and e:
|
||||
* 11. If the color of the node to the north of n is target-color, add that node to Q.
|
||||
* 12. If the color of the node to the south of n is target-color, add that node to Q.
|
||||
* 13. Continue looping until Q is exhausted.
|
||||
* 14. Return.
|
||||
*/
|
||||
var paintedPixels = [];
|
||||
var queue = [];
|
||||
var dy = [-1, 0, 1, 0];
|
||||
var dx = [0, 1, 0, -1];
|
||||
var targetColor;
|
||||
try {
|
||||
targetColor = frame.getPixel(col, row);
|
||||
} catch(e) {
|
||||
// Frame out of bound exception.
|
||||
}
|
||||
|
||||
if(targetColor == replacementColor) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
queue.push({"col": col, "row": row});
|
||||
var loopCount = 0;
|
||||
var cellCount = frame.getWidth() * frame.getHeight();
|
||||
while(queue.length > 0) {
|
||||
loopCount ++;
|
||||
|
||||
var currentItem = queue.pop();
|
||||
frame.setPixel(currentItem.col, currentItem.row, replacementColor);
|
||||
paintedPixels.push({"col": currentItem.col, "row": currentItem.row });
|
||||
|
||||
for (var i = 0; i < 4; i++) {
|
||||
var nextCol = currentItem.col + dx[i];
|
||||
var nextRow = currentItem.row + dy[i];
|
||||
try {
|
||||
if (frame.containsPixel(nextCol, nextRow) && frame.getPixel(nextCol, nextRow) == targetColor) {
|
||||
queue.push({"col": nextCol, "row": nextRow });
|
||||
}
|
||||
} catch(e) {
|
||||
// Frame out of bound exception.
|
||||
}
|
||||
}
|
||||
|
||||
// Security loop breaker:
|
||||
if(loopCount > 10 * cellCount) {
|
||||
console.log("loop breaker called");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return paintedPixels;
|
||||
},
|
||||
|
||||
/**
|
||||
* Calculate and return the maximal zoom level to display a picture in a given container.
|
||||
*
|
||||
* @param container jQueryObject Container where the picture should be displayed
|
||||
* @param number pictureHeight height in pixels of the picture to display
|
||||
* @param number pictureWidth width in pixels of the picture to display
|
||||
* @return number maximal zoom
|
||||
*/
|
||||
calculateZoomForContainer : function (container, pictureHeight, pictureWidth) {
|
||||
return this.calculateZoom(container.height(), container.width(), pictureHeight, pictureWidth);
|
||||
},
|
||||
|
||||
/**
|
||||
* Calculate and return the maximal zoom to display a picture for a given height and width.
|
||||
*
|
||||
* @param height number Height available to display the picture
|
||||
* @param width number Width available to display the picture
|
||||
* @param number pictureHeight height in pixels of the picture to display
|
||||
* @param number pictureWidth width in pixels of the picture to display
|
||||
* @return number maximal zoom
|
||||
*/
|
||||
calculateZoom : function (height, width, pictureHeight, pictureWidth) {
|
||||
var heightRatio = Math.floor(height / pictureHeight),
|
||||
widthRatio = Math.floor(width / pictureWidth);
|
||||
|
||||
return Math.min(heightRatio, widthRatio);
|
||||
}
|
||||
};
|
||||
})();
|
||||
30
src/js/utils/Template.js
Normal file
30
src/js/utils/Template.js
Normal file
@@ -0,0 +1,30 @@
|
||||
(function () {
|
||||
var ns = $.namespace("pskl.utils");
|
||||
|
||||
ns.Template = {
|
||||
get : function (templateId) {
|
||||
var template = document.getElementById(templateId);
|
||||
if (template) {
|
||||
return template.innerHTML;
|
||||
} else {
|
||||
console.error("Could not find template for id :", templateId);
|
||||
}
|
||||
},
|
||||
|
||||
createFromHTML : function (html) {
|
||||
var dummyEl = document.createElement("div");
|
||||
dummyEl.innerHTML = html;
|
||||
return dummyEl.children[0];
|
||||
},
|
||||
|
||||
replace : function (template, dict) {
|
||||
for (var key in dict) {
|
||||
if (dict.hasOwnProperty(key)) {
|
||||
var value = dict[key];
|
||||
template = template.replace(new RegExp('\\{\\{'+key+'\\}\\}', 'g'), value);
|
||||
}
|
||||
}
|
||||
return template;
|
||||
}
|
||||
};
|
||||
})();
|
||||
20
src/js/utils/UserAgent.js
Normal file
20
src/js/utils/UserAgent.js
Normal file
@@ -0,0 +1,20 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.utils');
|
||||
var ua = navigator.userAgent;
|
||||
|
||||
ns.UserAgent = {
|
||||
isIE : /MSIE/i.test( ua ),
|
||||
isChrome : /Chrome/i.test( ua ),
|
||||
isFirefox : /Firefox/i.test( ua )
|
||||
};
|
||||
|
||||
ns.UserAgent.version = (function () {
|
||||
if (pskl.utils.UserAgent.isIE) {
|
||||
return parseInt(/MSIE\s?(\d+)/i.exec( ua )[1], 10);
|
||||
} else if (pskl.utils.UserAgent.isChrome) {
|
||||
return parseInt(/Chrome\/(\d+)/i.exec( ua )[1], 10);
|
||||
} else if (pskl.utils.UserAgent.isFirefox) {
|
||||
return parseInt(/Firefox\/(\d+)/i.exec( ua )[1], 10);
|
||||
}
|
||||
})();
|
||||
})();
|
||||
76
src/js/utils/UserSettings.js
Normal file
76
src/js/utils/UserSettings.js
Normal file
@@ -0,0 +1,76 @@
|
||||
(function () {
|
||||
var ns = $.namespace("pskl");
|
||||
|
||||
ns.UserSettings = {
|
||||
|
||||
SHOW_GRID : 'SHOW_GRID',
|
||||
CANVAS_BACKGROUND : 'CANVAS_BACKGROUND',
|
||||
|
||||
KEY_TO_DEFAULT_VALUE_MAP_ : {
|
||||
'SHOW_GRID' : false,
|
||||
'CANVAS_BACKGROUND' : 'medium-canvas-background'
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
cache_ : {},
|
||||
|
||||
/**
|
||||
* Static method to access a user defined settings value ot its default
|
||||
* value if not defined yet.
|
||||
*/
|
||||
get : function (key) {
|
||||
this.checkKeyValidity_(key);
|
||||
if (!(key in this.cache_)) {
|
||||
this.cache_[key] =
|
||||
this.readFromLocalStorage_(key) || this.readFromDefaults_(key);
|
||||
}
|
||||
return this.cache_[key];
|
||||
},
|
||||
|
||||
set : function (key, value) {
|
||||
this.checkKeyValidity_(key);
|
||||
this.cache_[key] = value;
|
||||
this.writeToLocalStorage_(key, value);
|
||||
|
||||
$.publish(Events.USER_SETTINGS_CHANGED, [key, value]);
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
readFromLocalStorage_ : function(key) {
|
||||
var value = window.localStorage[key];
|
||||
if (typeof value != "undefined") {
|
||||
value = JSON.parse(value);
|
||||
}
|
||||
return value;
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
writeToLocalStorage_ : function(key, value) {
|
||||
// TODO(grosbouddha): Catch storage exception here.
|
||||
window.localStorage[key] = JSON.stringify(value);
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
readFromDefaults_ : function (key) {
|
||||
return this.KEY_TO_DEFAULT_VALUE_MAP_[key];
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
checkKeyValidity_ : function(key) {
|
||||
if(!(key in this.KEY_TO_DEFAULT_VALUE_MAP_)) {
|
||||
// TODO(grosbouddha): Define error catching strategy and throw exception from here.
|
||||
console.log("UserSettings key <"+ key +"> not find in supported keys.");
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
||||
51
src/js/utils/core.js
Normal file
51
src/js/utils/core.js
Normal file
@@ -0,0 +1,51 @@
|
||||
jQuery.namespace = function() {
|
||||
var a=arguments, o=null, i, j, d;
|
||||
for (i=0; i<a.length; i=i+1) {
|
||||
d=a[i].split(".");
|
||||
o=window;
|
||||
for (j=0; j<d.length; j=j+1) {
|
||||
o[d[j]]=o[d[j]] || {};
|
||||
o=o[d[j]];
|
||||
}
|
||||
}
|
||||
return o;
|
||||
};
|
||||
|
||||
/**
|
||||
* Need a polyfill for PhantomJS
|
||||
*/
|
||||
if (typeof Function.prototype.bind !== "function") {
|
||||
Function.prototype.bind = function(scope) {
|
||||
"use strict";
|
||||
var _function = this;
|
||||
return function() {
|
||||
return _function.apply(scope, arguments);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @provide pskl.utils
|
||||
*
|
||||
* @require Constants
|
||||
*/
|
||||
(function() { // namespace: pskl.utils
|
||||
|
||||
var ns = $.namespace("pskl.utils");
|
||||
|
||||
ns.rgbToHex = function(r, g, b) {
|
||||
if (r > 255 || g > 255 || b > 255) {
|
||||
throw "Invalid color component";
|
||||
}
|
||||
|
||||
return ((r << 16) | (g << 8) | b).toString(16);
|
||||
};
|
||||
|
||||
ns.inherit = function(extendedObject, inheritFrom) {
|
||||
extendedObject.prototype = Object.create(inheritFrom.prototype);
|
||||
extendedObject.prototype.constructor = extendedObject;
|
||||
extendedObject.prototype.superclass = inheritFrom.prototype;
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
71
src/js/utils/serialization/Deserializer.js
Normal file
71
src/js/utils/serialization/Deserializer.js
Normal file
@@ -0,0 +1,71 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.utils.serialization');
|
||||
|
||||
ns.Deserializer = function (data, callback) {
|
||||
this.layersToLoad_ = 0;
|
||||
this.data_ = data;
|
||||
this.callback_ = callback;
|
||||
this.piskel_ = null;
|
||||
};
|
||||
|
||||
ns.Deserializer.deserialize = function (data, callback) {
|
||||
var deserializer;
|
||||
if (data.modelVersion == Constants.MODEL_VERSION) {
|
||||
deserializer = new ns.Deserializer(data, callback);
|
||||
} else if (data.modelVersion == 1) {
|
||||
deserializer = new ns.backward.Deserializer_v1(data, callback);
|
||||
} else {
|
||||
deserializer = new ns.backward.Deserializer_v0(data, callback);
|
||||
}
|
||||
deserializer.deserialize();
|
||||
};
|
||||
|
||||
ns.Deserializer.prototype.deserialize = function (name) {
|
||||
var data = this.data_;
|
||||
var piskelData = data.piskel;
|
||||
name = name || 'Deserialized piskel';
|
||||
|
||||
var descriptor = new pskl.model.piskel.Descriptor(name, '');
|
||||
this.piskel_ = new pskl.model.Piskel(piskelData.width, piskelData.height, descriptor);
|
||||
|
||||
this.layersToLoad_ = piskelData.layers.length;
|
||||
|
||||
piskelData.layers.forEach(function (serializedLayer) {
|
||||
var layer = this.deserializeLayer(serializedLayer);
|
||||
this.piskel_.addLayer(layer);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
ns.Deserializer.prototype.deserializeLayer = function (layerString) {
|
||||
var layerData = JSON.parse(layerString);
|
||||
var layer = new pskl.model.Layer(layerData.name);
|
||||
|
||||
// 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.createFromImage(image, layerData.frameCount);
|
||||
|
||||
// 6 - add each image to the layer
|
||||
frames.forEach(layer.addFrame.bind(layer));
|
||||
|
||||
this.onLayerLoaded_();
|
||||
}.bind(this);
|
||||
|
||||
// 3 - set the source of the image
|
||||
image.src = base64PNG;
|
||||
|
||||
// 4 - return a pointer to the new layer instance
|
||||
return layer;
|
||||
};
|
||||
|
||||
ns.Deserializer.prototype.onLayerLoaded_ = function () {
|
||||
this.layersToLoad_ = this.layersToLoad_ - 1;
|
||||
if (this.layersToLoad_ === 0) {
|
||||
this.callback_(this.piskel_);
|
||||
}
|
||||
};
|
||||
})();
|
||||
31
src/js/utils/serialization/Serializer.js
Normal file
31
src/js/utils/serialization/Serializer.js
Normal file
@@ -0,0 +1,31 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.utils');
|
||||
|
||||
ns.Serializer = {
|
||||
serializePiskel : function (piskel) {
|
||||
var serializedLayers = piskel.getLayers().map(function (l) {
|
||||
return pskl.utils.Serializer.serializeLayer(l);
|
||||
});
|
||||
return JSON.stringify({
|
||||
modelVersion : Constants.MODEL_VERSION,
|
||||
piskel : {
|
||||
height : piskel.getHeight(),
|
||||
width : piskel.getWidth(),
|
||||
layers : serializedLayers
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
serializeLayer : function (layer) {
|
||||
var frames = layer.getFrames();
|
||||
var renderer = new pskl.rendering.FramesheetRenderer(frames);
|
||||
var base64PNG = renderer.renderAsCanvas().toDataURL();
|
||||
|
||||
return JSON.stringify({
|
||||
name : layer.getName(),
|
||||
base64PNG : base64PNG,
|
||||
frameCount : frames.length
|
||||
});
|
||||
}
|
||||
};
|
||||
})();
|
||||
19
src/js/utils/serialization/backward/Deserializer_v0.js
Normal file
19
src/js/utils/serialization/backward/Deserializer_v0.js
Normal file
@@ -0,0 +1,19 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.utils.serialization.backward');
|
||||
|
||||
ns.Deserializer_v0 = function (data, callback) {
|
||||
this.data_ = data;
|
||||
this.callback_ = callback;
|
||||
};
|
||||
|
||||
ns.Deserializer_v0.prototype.deserialize = function () {
|
||||
var pixelGrids = this.data_;
|
||||
var frames = pixelGrids.map(function (grid) {
|
||||
return pskl.model.Frame.fromPixelGrid(grid);
|
||||
});
|
||||
var descriptor = new pskl.model.piskel.Descriptor('Deserialized piskel', '');
|
||||
var layer = pskl.model.Layer.fromFrames('Layer 1', frames);
|
||||
|
||||
this.callback_(pskl.model.Piskel.fromLayers([layer], descriptor));
|
||||
};
|
||||
})();
|
||||
37
src/js/utils/serialization/backward/Deserializer_v1.js
Normal file
37
src/js/utils/serialization/backward/Deserializer_v1.js
Normal file
@@ -0,0 +1,37 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.utils.serialization.backward');
|
||||
|
||||
ns.Deserializer_v1 = function (data, callback) {
|
||||
this.callback_ = callback;
|
||||
this.data_ = data;
|
||||
};
|
||||
|
||||
ns.Deserializer_v1.prototype.deserialize = function () {
|
||||
var piskelData = this.data_.piskel;
|
||||
var descriptor = new pskl.model.piskel.Descriptor('Deserialized piskel', '');
|
||||
var piskel = new pskl.model.Piskel(piskelData.width, piskelData.height, descriptor);
|
||||
|
||||
piskelData.layers.forEach(function (serializedLayer) {
|
||||
var layer = this.deserializeLayer(serializedLayer);
|
||||
piskel.addLayer(layer);
|
||||
}.bind(this));
|
||||
|
||||
this.callback_(piskel);
|
||||
};
|
||||
|
||||
ns.Deserializer_v1.prototype.deserializeLayer = function (layerString) {
|
||||
var layerData = JSON.parse(layerString);
|
||||
var layer = new pskl.model.Layer(layerData.name);
|
||||
layerData.frames.forEach(function (serializedFrame) {
|
||||
var frame = this.deserializeFrame(serializedFrame);
|
||||
layer.addFrame(frame);
|
||||
}.bind(this));
|
||||
|
||||
return layer;
|
||||
};
|
||||
|
||||
ns.Deserializer_v1.prototype.deserializeFrame = function (frameString) {
|
||||
var framePixelGrid = JSON.parse(frameString);
|
||||
return pskl.model.Frame.fromPixelGrid(framePixelGrid);
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user