Add interpolation when using the pen tool quickly

This commit is contained in:
Vince 2012-09-02 17:49:28 +02:00
parent 946444a16b
commit 9758aa62d9
4 changed files with 98 additions and 65 deletions

View File

@ -55,4 +55,41 @@
ns.BaseTool.prototype.removeCanvasOverlays = function () { ns.BaseTool.prototype.removeCanvasOverlays = function () {
$(".canvas-overlay").remove(); $(".canvas-overlay").remove();
}; };
/**
* Bresenham line algorihtm: Get an array of pixels from
* start and end coordinates.
*
* http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
* http://stackoverflow.com/questions/4672279/bresenham-algorithm-in-javascript
*
* @private
*/
ns.BaseTool.prototype.getLinePixels_ = function(x0, x1, y0, y1) {
var pixels = [];
var dx = Math.abs(x1-x0);
var dy = Math.abs(y1-y0);
var sx = (x0 < x1) ? 1 : -1;
var sy = (y0 < y1) ? 1 : -1;
var err = dx-dy;
while(true){
// Do what you need to for this
pixels.push({"col": x0, "row": y0});
if ((x0==x1) && (y0==y1)) break;
var e2 = 2*err;
if (e2>-dy){
err -= dy;
x0 += sx;
}
if (e2 < dx) {
err += dx;
y0 += sy;
}
}
return pixels;
};
})(); })();

View File

@ -10,6 +10,9 @@
this.toolId = "tool-pen" this.toolId = "tool-pen"
}; };
this.previousCol = null;
this.previousRow = null;
pskl.utils.inherit(ns.SimplePen, ns.BaseTool); pskl.utils.inherit(ns.SimplePen, ns.BaseTool);
/** /**
@ -17,6 +20,9 @@
*/ */
ns.SimplePen.prototype.applyToolAt = function(col, row, frame, color, canvas, dpi) { ns.SimplePen.prototype.applyToolAt = function(col, row, frame, color, canvas, dpi) {
this.previousCol = col;
this.previousRow = row;
// Change model: // Change model:
var color = pskl.utils.normalizeColor(color); var color = pskl.utils.normalizeColor(color);
if (color != frame[col][row]) { if (color != frame[col][row]) {
@ -29,6 +35,21 @@
}; };
ns.SimplePen.prototype.moveToolAt = function(col, row, frame, color, canvas, dpi) { ns.SimplePen.prototype.moveToolAt = function(col, row, frame, color, canvas, dpi) {
this.applyToolAt(col, row, frame, color, canvas, dpi);
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
// current point and the previously drawn one.
// 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<l; i++) {
this.applyToolAt(interpolatedPixels[i].col, interpolatedPixels[i].row, frame, color, canvas, dpi);
}
}
else {
this.applyToolAt(col, row, frame, color, canvas, dpi);
}
this.previousCol = col;
this.previousRow = row;
}; };
})(); })();

View File

@ -99,41 +99,4 @@
this.removeCanvasOverlays(); this.removeCanvasOverlays();
}; };
/**
* Bresenham line algorihtm: Get an array of pixels from
* start and end coordinates.
*
* http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
* http://stackoverflow.com/questions/4672279/bresenham-algorithm-in-javascript
*
* @private
*/
ns.Stroke.prototype.getLinePixels_ = function(x0, x1, y0, y1) {
var pixels = [];
var dx = Math.abs(x1-x0);
var dy = Math.abs(y1-y0);
var sx = (x0 < x1) ? 1 : -1;
var sy = (y0 < y1) ? 1 : -1;
var err = dx-dy;
while(true){
// Do what you need to for this
pixels.push({"col": x0, "row": y0});
if ((x0==x1) && (y0==y1)) break;
var e2 = 2*err;
if (e2>-dy){
err -= dy;
x0 += sx;
}
if (e2 < dx) {
err += dx;
y0 += sy;
}
}
return pixels;
};
})(); })();

View File

@ -11,18 +11,23 @@ $.namespace("pskl");
DEFAULT_PEN_COLOR = '#000000', DEFAULT_PEN_COLOR = '#000000',
PISKEL_SERVICE_URL = 'http://2.piskel-app.appspot.com', PISKEL_SERVICE_URL = 'http://2.piskel-app.appspot.com',
// Temporary zoom implementation to easily get bigger canvases to
// see how good perform critical algorithms on big canvas.
zoom = 1,
// Configuration: // Configuration:
// Canvas size in pixel size (not dpi related) // Canvas size in pixel size (not dpi related)
framePixelWidth = 32, framePixelWidth = 32 * zoom,
framePixelHeight = 32, framePixelHeight = 32 * zoom,
// Scaling factors for a given frameSheet rendering: // Scaling factors for a given frameSheet rendering:
// Main drawing area: // Main drawing area:
drawingCanvasDpi = 20, drawingCanvasDpi = Math.ceil(20/ zoom),
// Canvas previous in the slideshow: // Canvas previous in the slideshow:
previewTileCanvasDpi = 4, previewTileCanvasDpi = Math.ceil(4 / zoom),
// Ainmated canvas preview: // Ainmated canvas preview:
previewAnimationCanvasDpi = 8, previewAnimationCanvasDpi = Math.ceil(8 / zoom),
// DOM references: // DOM references:
drawingAreaContainer, drawingAreaContainer,
@ -39,6 +44,7 @@ $.namespace("pskl");
paletteColors = [], paletteColors = [],
currentFrame = null; currentFrame = null;
currentToolBehavior = null, currentToolBehavior = null,
previousMousemoveTime = 0,
//utility //utility
_normalizeColor = function (color) { _normalizeColor = function (color) {
@ -131,21 +137,22 @@ $.namespace("pskl");
}, },
removeMessage : function () { removeMessage : function () {
var message = $("user-message"); var message = $("#user-message");
if (message) { if (message.length) {
message.parentNode.removeChild(message); message.remove();
} }
}, },
persistToLocalStorageRequest: function() { persistToLocalStorageRequest: function() {
// Persist to localStorage when drawing. We throttle localStorage accesses // Persist to localStorage when drawing. We throttle localStorage accesses
// for high frequency drawing (eg mousemove). // for high frequency drawing (eg mousemove).
if(localStorageThrottler == null) { if(localStorageThrottler != null) {
localStorageThrottler = window.setTimeout(function() { window.clearTimeout(localStorageThrottler);
piskel.persistToLocalStorage();
localStorageThrottler = null;
}, 1000);
} }
localStorageThrottler = window.setTimeout(function() {
piskel.persistToLocalStorage();
localStorageThrottler = null;
}, 1000);
}, },
persistToLocalStorage: function() { persistToLocalStorage: function() {
@ -394,20 +401,25 @@ $.namespace("pskl");
onCanvasMousemove : function (event) { onCanvasMousemove : function (event) {
//this.updateCursorInfo(event); //this.updateCursorInfo(event);
if (isClicked) { var currentTime = new Date().getTime();
var spriteCoordinate = this.getSpriteCoordinate(event); // Throttling of the mousemove event:
currentToolBehavior.moveToolAt( if ((currentTime - previousMousemoveTime) > 40 ) {
spriteCoordinate.col, if (isClicked) {
spriteCoordinate.row, var spriteCoordinate = this.getSpriteCoordinate(event);
currentFrame, currentToolBehavior.moveToolAt(
penColor, spriteCoordinate.col,
drawingAreaCanvas, spriteCoordinate.row,
drawingCanvasDpi); currentFrame,
penColor,
drawingAreaCanvas,
drawingCanvasDpi);
// TODO(vincz): Find a way to move that to the model instead of being at the interaction level. // 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, // Eg when drawing, it may make sense to have it here. However for a non drawing tool,
// you don't need to draw anything when mousemoving and you request useless localStorage. // you don't need to draw anything when mousemoving and you request useless localStorage.
piskel.persistToLocalStorageRequest(); piskel.persistToLocalStorageRequest();
}
previousMousemoveTime = currentTime;
} }
}, },