diff --git a/css/style.css b/css/style.css
index fc983431..7859b01a 100644
--- a/css/style.css
+++ b/css/style.css
@@ -127,6 +127,13 @@ ul, li {
z-index: 1;
}
+.canvas-main {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 1;
+}
+
.canvas-overlay {
position: absolute;
top: 0;
diff --git a/index.html b/index.html
index 209d828a..f4cca82f 100644
--- a/index.html
+++ b/index.html
@@ -75,6 +75,9 @@
+
+
+
diff --git a/js/FrameSheetModel.js b/js/FrameSheetModel.js
index efd77a4e..920d7445 100644
--- a/js/FrameSheetModel.js
+++ b/js/FrameSheetModel.js
@@ -8,24 +8,6 @@ pskl.FrameSheetModel = (function() {
var width;
var height;
- /**
- * Create empty frame of dimension [width * height] with Constants.TRANSPARENT_COLOR
- * as a default value.
- *
- * @private
- */
- var createEmptyFrame_ = function() {
- var emptyFrame = []; //new Array(width);
- for (var columnIndex=0; columnIndex < width; columnIndex++) {
- var columnArray = [];
- for(var heightIndex = 0; heightIndex < height; heightIndex++) {
- columnArray.push(Constants.TRANSPARENT_COLOR);
- }
- emptyFrame[columnIndex] = columnArray;
- }
- return emptyFrame;
- };
-
/**
* @private
*/
@@ -38,33 +20,15 @@ pskl.FrameSheetModel = (function() {
return true; // I'm always right dude
},
- getAllPixels : function () {
- var pixels = [];
- for (var i = 0 ; i < frames.length ; i++) {
- pixels = pixels.concat(this._getFramePixels(frames[i]));
- }
- return pixels;
- },
-
- _getFramePixels : function (frame) {
- var pixels = [];
- for (var i = 0 ; i < frame.length ; i++) {
- var line = frame[i];
- for (var j = 0 ; j < line.length ; j++) {
- pixels.push(line[j]);
- }
- }
- return pixels;
- },
-
getUsedColors: function() {
var colors = {};
for (var frameIndex=0; frameIndex < frames.length; frameIndex++) {
- var currentFrame = frames[frameIndex];
- for (var i = 0 ; i < currentFrame.length ; i++) {
- var line = currentFrame[i];
- for (var j = 0 ; j < line.length ; j++) {
- colors[line[j]] = line[j];
+ var frame = frames[frameIndex];
+ for (var i = 0, width = frame.getWidth(); i < width ; i++) {
+ var line = frame[i];
+ for (var j = 0, height = frame.getHeight() ; j < height ; j++) {
+ var pixel = frame.getPixel(i, j);
+ colors[pixel] = pixel;
}
}
}
@@ -92,7 +56,7 @@ pskl.FrameSheetModel = (function() {
},
addEmptyFrame: function() {
- frames.push(createEmptyFrame_());
+ frames.push(pskl.rendering.Frame.createEmpty(width, height));
},
getFrameCount: function() {
@@ -101,9 +65,9 @@ pskl.FrameSheetModel = (function() {
getFrameByIndex: function(index) {
if (isNaN(index)) {
- throw "Bad argument value for getFrameByIndex method: <" + index + ">"
+ throw "Bad argument value for getFrameByIndex method: <" + index + ">";
} else if (index < 0 || index > frames.length) {
- throw "Out of bound index for frameSheet object."
+ throw "Out of bound index for frameSheet object.";
}
return frames[index];
@@ -116,17 +80,12 @@ pskl.FrameSheetModel = (function() {
frames.splice(index, 1);
},
- duplicateFrameByIndex: function(frameToDuplicateIndex) {
- var frame = inst.getFrameByIndex(frameToDuplicateIndex);
- var clonedFrame = [];
- for(var i=0, l=frame.length; i= frame_.length ||
- row_ < 0 ||
- row_ >= frame_[0].length) {
- return false;
- }
- return true;
- }
-
queue.push({"col": col, "row": row});
var loopCount = 0;
- var cellCount = frame.length * frame[0].length;
+ var cellCount = frame.getWidth() * frame.getHeight();
while(queue.length > 0) {
loopCount ++;
var currentItem = queue.pop();
- frame[currentItem.col][currentItem.row] = replacementColor;
+ frame.setPixel(currentItem.col, currentItem.row, replacementColor);
for (var i = 0; i < 4; i++) {
var nextCol = currentItem.col + dx[i]
var nextRow = currentItem.row + dy[i]
try {
- if (isInFrameBound(frame, nextCol, nextRow) && frame[nextCol][nextRow] == targetColor) {
+ if (frame.isInFrame(nextCol, nextRow) && frame.getPixel(nextCol, nextRow) == targetColor) {
queue.push({"col": nextCol, "row": nextRow });
}
} catch(e) {
diff --git a/js/drawingtools/Rectangle.js b/js/drawingtools/Rectangle.js
index d3c9622a..85dce1ac 100644
--- a/js/drawingtools/Rectangle.js
+++ b/js/drawingtools/Rectangle.js
@@ -12,11 +12,6 @@
// Rectangle's first point coordinates (set in applyToolAt)
this.startCol = null;
this.startRow = null;
- // Rectangle's second point coordinates (changing dynamically in moveToolAt)
- this.endCol = null;
- this.endRow = null;
-
- this.canvasOverlay = null;
};
pskl.utils.inherit(ns.Rectangle, ns.BaseTool);
@@ -24,26 +19,22 @@
/**
* @override
*/
- ns.Rectangle.prototype.applyToolAt = function(col, row, frame, color, canvas, dpi) {
+ ns.Rectangle.prototype.applyToolAt = function(col, row, color, drawer) {
this.startCol = col;
this.startRow = row;
- // The fake canvas where we will draw the preview of the rectangle:
- this.canvasOverlay = this.createCanvasOverlay(canvas);
// Drawing the first point of the rectangle in the fake overlay canvas:
- this.drawPixelInCanvas(col, row, this.canvasOverlay, color, dpi);
+ drawer.overlay.setPixel(col, row, color);
+ drawer.renderOverlay();
};
- ns.Rectangle.prototype.moveToolAt = function(col, row, frame, color, canvas, dpi) {
- this.endCol = col;
- this.endRow = row;
- // When the user moussemove (before releasing), we dynamically compute the
- // pixel to draw the line and draw this line in the overlay canvas:
- var strokePoints = this.getRectanglePixels_(this.startCol, this.endCol, this.startRow, this.endRow);
-
+ ns.Rectangle.prototype.moveToolAt = function(col, row, color, drawer) {
// Clean overlay canvas:
- this.canvasOverlay.getContext("2d").clearRect(
- 0, 0, this.canvasOverlay.width, this.canvasOverlay.height);
+ drawer.clearOverlay();
+
+ // When the user moussemove (before releasing), we dynamically compute the
+ // pixel to draw the line and draw this line in the overlay :
+ var strokePoints = this.getRectanglePixels_(this.startCol, col, this.startRow, row);
// Drawing current stroke:
for(var i = 0; i< strokePoints.length; i++) {
@@ -51,39 +42,29 @@
if(color == Constants.TRANSPARENT_COLOR) {
color = Constants.SELECTION_TRANSPARENT_COLOR;
}
- this.drawPixelInCanvas(strokePoints[i].col, strokePoints[i].row, this.canvasOverlay, color, dpi);
+ drawer.overlay.setPixel(strokePoints[i].col, strokePoints[i].row, color);
}
+ drawer.renderOverlay();
};
/**
* @override
*/
- ns.Rectangle.prototype.releaseToolAt = function(col, row, frame, color, canvas, dpi) {
- this.endCol = col;
- this.endRow = row;
-
+ ns.Rectangle.prototype.releaseToolAt = function(col, row, color, drawer) {
// If the stroke tool is released outside of the canvas, we cancel the stroke:
- // TODO: Mutualize this check in common method
- if(col < 0 || row < 0 || col > frame.length || row > frame[0].length) {
- this.removeCanvasOverlays();
- return;
- }
-
- // The user released the tool to draw a line. We will compute the pixel coordinate, impact
- // the model and draw them in the drawing canvas (not the fake overlay anymore)
- var strokePoints = this.getRectanglePixels_(this.startCol, this.endCol, this.startRow, this.endRow);
-
- for(var i = 0; i< strokePoints.length; i++) {
- // Change model:
- frame[strokePoints[i].col][strokePoints[i].row] = color;
-
+ if(drawer.frame.isInFrame(col, row)) {
+ var strokePoints = this.getRectanglePixels_(this.startCol, col, this.startRow, row);
+ for(var i = 0; i< strokePoints.length; i++) {
+ // Change model:
+ drawer.frame.setPixel(strokePoints[i].col, strokePoints[i].row, color);
+ }
+ // The user released the tool to draw a line. We will compute the pixel coordinate, impact
+ // the model and draw them in the drawing canvas (not the fake overlay anymore)
// Draw in canvas:
// TODO: Remove that when we have the centralized redraw loop
- this.drawPixelInCanvas(strokePoints[i].col, strokePoints[i].row, canvas, color, dpi);
+ drawer.renderFrame();
}
-
- // For now, we are done with the stroke tool and don't need an overlay anymore:
- this.removeCanvasOverlays();
+ drawer.clearOverlay();
};
/**
diff --git a/js/drawingtools/SimplePen.js b/js/drawingtools/SimplePen.js
index 309df10a..ec5d5485 100644
--- a/js/drawingtools/SimplePen.js
+++ b/js/drawingtools/SimplePen.js
@@ -7,7 +7,7 @@
var ns = $.namespace("pskl.drawingtools");
ns.SimplePen = function() {
- this.toolId = "tool-pen"
+ this.toolId = "tool-pen";
};
this.previousCol = null;
@@ -18,20 +18,17 @@
/**
* @override
*/
- ns.SimplePen.prototype.applyToolAt = function(col, row, frame, color, canvas, dpi) {
-
+ ns.SimplePen.prototype.applyToolAt = function(col, row, color, drawer) {
this.previousCol = col;
this.previousRow = row;
- if (color != frame[col][row]) {
- frame[col][row] = color;
- }
+ drawer.frame.setPixel(col, row, color);
// Draw on canvas:
// TODO: Remove that when we have the centralized redraw loop
- this.drawPixelInCanvas(col, row, canvas, color, dpi);
+ drawer.renderFramePixel(col, row);
};
- ns.SimplePen.prototype.moveToolAt = function(col, row, frame, color, canvas, dpi) {
+ ns.SimplePen.prototype.moveToolAt = function(col, row, color, drawer) {
if((Math.abs(col - this.previousCol) > 1) || (Math.abs(row - this.previousRow) > 1)) {
// The pen movement is too fast for the mousemove frequency, there is a gap between the
@@ -39,11 +36,11 @@
// We fill the gap by calculating missing dots (simple linear interpolation) and draw them.
var interpolatedPixels = this.getLinePixels_(col, this.previousCol, row, this.previousRow);
for(var i=0, l=interpolatedPixels.length; i frame.length || row > frame[0].length) {
- this.removeCanvasOverlays();
- return;
- }
-
- // The user released the tool to draw a line. We will compute the pixel coordinate, impact
- // the model and draw them in the drawing canvas (not the fake overlay anymore)
- var strokePoints = this.getLinePixels_(this.startCol, this.endCol, this.startRow, this.endRow);
-
- for(var i = 0; i< strokePoints.length; i++) {
- // Change model:
- frame[strokePoints[i].col][strokePoints[i].row] = color;
-
+ if(drawer.frame.isInFrame(col, row)) {
+ // The user released the tool to draw a line. We will compute the pixel coordinate, impact
+ // the model and draw them in the drawing canvas (not the fake overlay anymore)
+ var strokePoints = this.getLinePixels_(this.startCol, col, this.startRow, row);
+ for(var i = 0; i< strokePoints.length; i++) {
+ // Change model:
+ drawer.updateFrame(strokePoints[i].col, strokePoints[i].row, color);
+ }
// Draw in canvas:
// TODO: Remove that when we have the centralized redraw loop
- this.drawPixelInCanvas(strokePoints[i].col, strokePoints[i].row, canvas, color, dpi);
- }
-
+ drawer.renderFrame();
+ }
// For now, we are done with the stroke tool and don't need an overlay anymore:
- this.removeCanvasOverlays();
+ drawer.clearOverlay();
};
-
})();
diff --git a/js/piskel.js b/js/piskel.js
index fe172179..2554f008 100644
--- a/js/piskel.js
+++ b/js/piskel.js
@@ -89,6 +89,13 @@ $.namespace("pskl");
this.initPreviewSlideshow();
this.initAnimationPreview();
this.startAnimation();
+
+ var frame = frameSheet.getFrameByIndex(this.getActiveFrameIndex());
+ this.drawer = new pskl.rendering.DrawingController(
+ frame,
+ $('#drawing-canvas-container')[0],
+ drawingCanvasDpi
+ );
pskl.ToolSelector.init();
pskl.Palette.init(frameSheet);
@@ -153,23 +160,13 @@ $.namespace("pskl");
initDrawingArea : function() {
drawingAreaContainer = $('#drawing-canvas-container')[0];
-
- drawingAreaCanvas = document.createElement("canvas");
- drawingAreaCanvas.className = 'canvas';
- drawingAreaCanvas.setAttribute('width', '' + framePixelWidth * drawingCanvasDpi);
- drawingAreaCanvas.setAttribute('height', '' + framePixelHeight * drawingCanvasDpi);
-
- drawingAreaContainer.setAttribute('style',
- 'width:' + framePixelWidth * drawingCanvasDpi + 'px; height:' + framePixelHeight * drawingCanvasDpi + 'px;');
-
- drawingAreaCanvas.setAttribute('oncontextmenu', 'piskel.onCanvasContextMenu(event)');
- drawingAreaContainer.appendChild(drawingAreaCanvas);
-
var body = document.getElementsByTagName('body')[0];
body.setAttribute('onmouseup', 'piskel.onDocumentBodyMouseup(event)');
+ drawingAreaContainer.style.width = framePixelWidth * drawingCanvasDpi + "px";
+ drawingAreaContainer.style.height = framePixelHeight * drawingCanvasDpi + "px";
+ drawingAreaContainer.setAttribute('oncontextmenu', 'piskel.onCanvasContextMenu(event)');
drawingAreaContainer.setAttribute('onmousedown', 'piskel.onCanvasMousedown(event)');
drawingAreaContainer.setAttribute('onmousemove', 'piskel.onCanvasMousemove(event)');
- this.drawFrameToCanvas(currentFrame, drawingAreaCanvas, drawingCanvasDpi);
},
initPreviewSlideshow: function() {
@@ -318,12 +315,11 @@ $.namespace("pskl");
}
var spriteCoordinate = this.getSpriteCoordinate(event);
currentToolBehavior.applyToolAt(
- spriteCoordinate.col,
- spriteCoordinate.row,
- currentFrame,
- penColor,
- drawingAreaCanvas,
- drawingCanvasDpi);
+ spriteCoordinate.col,
+ spriteCoordinate.row,
+ penColor,
+ this.drawer
+ );
$.publish(Events.LOCALSTORAGE_REQUEST);
},
@@ -337,12 +333,11 @@ $.namespace("pskl");
if (isClicked) {
var spriteCoordinate = this.getSpriteCoordinate(event);
currentToolBehavior.moveToolAt(
- spriteCoordinate.col,
- spriteCoordinate.row,
- currentFrame,
- penColor,
- drawingAreaCanvas,
- drawingCanvasDpi);
+ spriteCoordinate.col,
+ spriteCoordinate.row,
+ penColor,
+ this.drawer
+ );
// TODO(vincz): Find a way to move that to the model instead of being at the interaction level.
// Eg when drawing, it may make sense to have it here. However for a non drawing tool,
@@ -372,10 +367,9 @@ $.namespace("pskl");
currentToolBehavior.releaseToolAt(
spriteCoordinate.col,
spriteCoordinate.row,
- currentFrame,
penColor,
- drawingAreaCanvas,
- drawingCanvasDpi);
+ this.drawer
+ );
},
// TODO(vincz/julz): Refactor to make this disappear in a big event-driven redraw loop
@@ -408,7 +402,7 @@ $.namespace("pskl");
},
getRelativeCoordinates : function (x, y) {
- var canvasRect = drawingAreaCanvas.getBoundingClientRect();
+ var canvasRect = $(".canvas-main")[0].getBoundingClientRect();
return {
x : x - canvasRect.left,
y : y - canvasRect.top
diff --git a/js/rendering/DrawingController.js b/js/rendering/DrawingController.js
new file mode 100644
index 00000000..f12fafe1
--- /dev/null
+++ b/js/rendering/DrawingController.js
@@ -0,0 +1,64 @@
+(function () {
+ var ns = $.namespace("pskl.rendering");
+ ns.DrawingController = function (frame, container, dpi) {
+ this.dpi = dpi;
+
+ // Public
+ this.frame = frame;
+ this.overlay = ns.Frame.createEmptyFromFrame(frame);
+
+ // Private
+ this.container = container;
+ this.mainCanvas = this.createMainCanvas();
+ this.overlayCanvas = this.createOverlayCanvas();
+ this.renderer = new ns.FrameRenderer();
+ };
+
+ ns.DrawingController.prototype.renderFrame = function () {
+ this.renderer.render(this.frame, this.mainCanvas, this.dpi);
+ };
+
+ ns.DrawingController.prototype.renderFramePixel = function (col, row) {
+ this.renderer.drawPixel(col, row, this.frame, this.mainCanvas, this.dpi);
+ };
+
+ ns.DrawingController.prototype.renderOverlay = function () {
+ this.renderer.render(this.overlay, this.overlayCanvas, this.dpi);
+ };
+
+ ns.DrawingController.prototype.clearOverlay = function () {
+ this.overlay = ns.Frame.createEmptyFromFrame(this.frame);
+ this.overlayCanvas.getContext("2d").clearRect(0, 0, this.overlayCanvas.width, this.overlayCanvas.height);
+ };
+
+ ns.DrawingController.prototype.createMainCanvas = function () {
+ var mainCanvas = this.createCanvas();
+ mainCanvas.className = "canvas-main";
+ this.container.appendChild(mainCanvas);
+ return mainCanvas;
+ };
+
+ // For some tools, we need a fake canvas that overlay the drawing canvas. These tools are
+ // generally 'drap and release' based tools (stroke, selection, etc) and the fake canvas
+ // will help to visualize the tool interaction (without modifying the canvas).
+ ns.DrawingController.prototype.createOverlayCanvas = function () {
+ var overlayCanvas = this.createCanvas();
+ overlayCanvas.className = "canvas-overlay";
+ this.container.appendChild(overlayCanvas);
+ return overlayCanvas;
+ };
+
+ // For some tools, we need a fake canvas that overlay the drawing canvas. These tools are
+ // generally 'drap and release' based tools (stroke, selection, etc) and the fake canvas
+ // will help to visualize the tool interaction (without modifying the canvas).
+ ns.DrawingController.prototype.createCanvas = function () {
+ var width = this.frame.getWidth(),
+ height = this.frame.getHeight();
+
+ var canvas = document.createElement("canvas");
+ canvas.setAttribute("width", width * this.dpi);
+ canvas.setAttribute("height", height * this.dpi);
+
+ return canvas;
+ };
+})();
\ No newline at end of file
diff --git a/js/rendering/Frame.js b/js/rendering/Frame.js
new file mode 100644
index 00000000..93d90c27
--- /dev/null
+++ b/js/rendering/Frame.js
@@ -0,0 +1,53 @@
+(function () {
+ var ns = $.namespace("pskl.rendering");
+ ns.Frame = function (pixels) {
+ this.pixels = pixels;
+ };
+
+ ns.Frame.createEmpty = function (width, height) {
+ var pixels = []; //new Array(width);
+ for (var columnIndex=0; columnIndex < width; columnIndex++) {
+ var columnArray = [];
+ for(var heightIndex = 0; heightIndex < height; heightIndex++) {
+ columnArray.push(Constants.TRANSPARENT_COLOR);
+ }
+ pixels[columnIndex] = columnArray;
+ }
+ return new ns.Frame(pixels);
+ };
+
+ ns.Frame.createEmptyFromFrame = function (frame) {
+ return ns.Frame.createEmpty(frame.getWidth(), frame.getHeight());
+ };
+
+ ns.Frame.prototype.clone = function () {
+ var clone = ns.Frame.createEmptyFromFrame(this);
+ for (var col = 0 ; col < clone.getWidth() ; col++) {
+ for (var row = 0 ; row < clone.getHeight() ; row++) {
+ clone.setPixel(col, row, this.getPixel(col, row));
+ }
+ }
+ return clone;
+ };
+
+ ns.Frame.prototype.setPixel = function (col, row, color) {
+ this.pixels[col][row] = color;
+ };
+
+ ns.Frame.prototype.getPixel = function (col, row) {
+ return this.pixels[col][row];
+ };
+
+ ns.Frame.prototype.getWidth = function () {
+ return this.pixels.length;
+ };
+
+ ns.Frame.prototype.getHeight = function () {
+ return this.pixels[0].length;
+ };
+
+ ns.Frame.prototype.isInFrame = function (col, row) {
+ return col >= 0 && row >= 0 && col <= this.pixels.length && row <= this.pixels[0].length;
+ };
+
+})();
\ No newline at end of file
diff --git a/js/rendering/FrameRenderer.js b/js/rendering/FrameRenderer.js
index f5c07c54..8b630579 100644
--- a/js/rendering/FrameRenderer.js
+++ b/js/rendering/FrameRenderer.js
@@ -1,31 +1,28 @@
-(function () {
- var ns = $.namespace("pskl.rendering");
- ns.FrameRenderer = function () {};
-
- ns.FrameRenderer.prototype.render = function (frame, canvas, dpi) {
- var color;
- for(var col = 0, num_col = frame.length; col < num_col; col++) {
- for(var row = 0, num_row = frame[col].length; row < num_row; row++) {
- color = frame[col][row];
- this.drawPixelInCanvas(col, row, canvas, color, dpi);
- }
- }
- };
-
- ns.FrameRenderer.prototype.drawPixelInCanvas = function () {
- var context = canvas.getContext('2d');
- if(color == Constants.TRANSPARENT_COLOR) {
- context.clearRect(col * dpi, row * dpi, dpi, dpi);
- }
- else {
- if(color != Constants.SELECTION_TRANSPARENT_COLOR) {
- // TODO(vincz): Found a better design to update the palette, it's called too frequently.
- $.publish(Events.COLOR_USED, [color]);
- }
- context.fillStyle = color;
- context.fillRect(col * dpi, row * dpi, dpi, dpi);
- }
-
- }
-
+(function () {
+ var ns = $.namespace("pskl.rendering");
+ ns.FrameRenderer = function () {};
+
+ ns.FrameRenderer.prototype.render = function (frame, canvas, dpi) {
+ for(var col = 0, width = frame.getWidth(); col < width; col++) {
+ for(var row = 0, height = frame.getHeight(); row < height; row++) {
+ this.drawPixel(col, row, frame, canvas, dpi);
+ }
+ }
+ };
+
+ ns.FrameRenderer.prototype.drawPixel = function (col, row, frame, canvas, dpi) {
+ var context = canvas.getContext('2d');
+ var color = frame.getPixel(col, row);
+ if(color == Constants.TRANSPARENT_COLOR) {
+ context.clearRect(col * dpi, row * dpi, dpi, dpi);
+ }
+ else {
+ if(color != Constants.SELECTION_TRANSPARENT_COLOR) {
+ // TODO(vincz): Found a better design to update the palette, it's called too frequently.
+ $.publish(Events.COLOR_USED, [color]);
+ }
+ context.fillStyle = color;
+ context.fillRect(col * dpi, row * dpi, dpi, dpi);
+ }
+ };
})();
\ No newline at end of file