Reputation: 313
I am creating phonegap/cordova android app for canvas drawing.
I have replaced CordovaWebView(android stock browser webview) with https://github.com/ludei/webview-plus/ webview+ to improve the drawing performance on android platform. After replacing webview drawing is smooth as fast on android.
Now i am creating undo functionality using below demo as base
http://www.createjs.com/Demos/EaselJS/CurveTo
Undo functionality is working as expected, but undo performance is gradually decreasing with more drawing data on canvas. This gradual performance decrease is absolutely certain because of the approach i followed for undoing. On desktop browser performance is good but on android mobile and tablet undoing is taking long time.
My approach for undo :
Storing(pushing) each drawn point into a points[] array along with stroke width and color. This points[] array stores all information for one continuous line.
Then i am storing(pushing) each continuous line information in allPoints array on mouseup event.
Then on clicking undo, I popped(removed) last continuous line from allPoints[] array and then update stage by redrawing all points from allPoints[] array.
As canvas drawing strokes increasing, allPoints[] array size increase and it is taking more time to redraw allPoints.
I have setup a fiddle for demo of my undo approach.
http://jsfiddle.net/JTqvJ/188/
var canvas, stage;
var drawingCanvas;
var oldPt;
var oldMidPt;
var title;
var color;
var stroke;
var colors;
var index;
var allPoints = [];
var points = [];
function init() {
canvas = document.getElementById("canvas");
var undoEl = document.getElementById('undo');
undoEl.addEventListener("click",undoDrawing);
index = 0;
colors = ["#828b20"];
//check to see if we are running in a browser with touch support
stage = new createjs.Stage(canvas);
stage.autoClear = false;
stage.enableDOMEvents(true);
createjs.Touch.enable(stage);
createjs.Ticker.setFPS(24);
drawingCanvas = new createjs.Shape();
stage.addEventListener("stagemousedown", handleMouseDown);
stage.addEventListener("stagemouseup", handleMouseUp);
stage.addChild(drawingCanvas);
stage.update();
}
function stop() {
}
function handleMouseDown(event) {
color = colors[(index++) % colors.length];
stroke = 2;
oldPt = new createjs.Point(stage.mouseX, stage.mouseY);
oldMidPt = oldPt.clone();
stage.addEventListener("stagemousemove", handleMouseMove);
}
function handleMouseMove(event) {
var midPt = new createjs.Point(oldPt.x + stage.mouseX >> 1, oldPt.y + stage.mouseY >> 1);
// current point to draw
var point = {
midPt_x: midPt.x,
midPt_y: midPt.y,
oldPt_x: oldPt.x,
oldPt_y: oldPt.y,
oldMidPt_x:oldMidPt.x,
oldMidPt_y: oldMidPt.y,
s_stroke: stroke,
s_color: color,
}
//store this point in points array
points.push(point);
//draw this point
drawLine(point);
oldPt.x = stage.mouseX;
oldPt.y = stage.mouseY;
oldMidPt.x = midPt.x;
oldMidPt.y = midPt.y;
stage.update();
}
function handleMouseUp(event) {
stage.removeEventListener("stagemousemove", handleMouseMove);
allPoints.push(points);
//empty points array for saving new point objects
points = [];
console.log(allPoints);
}
function drawLine(point){
console.log("drawing");
drawingCanvas.graphics.clear().setStrokeStyle(point.s_stroke, 'round', 'round').beginStroke(point.s_color).moveTo(point.midPt_x, point.midPt_y).curveTo(point.oldPt_x, point.oldPt_y, point.oldMidPt_x, point.oldMidPt_y);
};
function reDrawAllLines(){
//clear whole canvas to refresh
stage.clear();
for (var index1 in allPoints) {
for(var index2 in allPoints[index1]){
drawLine(allPoints[index1][index2]);
stage.update();
//alert(allPoints[index1][index2]);
}
}
};
function undoDrawing(){
console.log('undo');
if(allPoints.length > 0){
console.log('pop last one');
//pop/remove last continuous line from allPoints
allPoints.pop();
allPoints.pop();
console.log(allPoints);
//redraw allPoints array to refresh canvas
reDrawAllLines();
}
}
init();
I thought redrawing allPoints will be a better approach than saving snapshot of canvas drawing as image for undoing as it will be a heavy memory consuming process, but now redrawing allPoints is slow, my bad luck :(
Am i doing something wrong in my undo functionality because of which canvas taking long time to redraw drawing from allPoints[] array. Undo is dead slow on android mobile after 10 or more lines.
Can someone help me with better approach for undoing with heavy canvas drawing?
Note: My canvas is A4 page size i.e height is 1123px and width is 794px .
Upvotes: 0
Views: 2063
Reputation: 313
I managed to get fast drawing and fast undo with easelJs.
Instead of saving each point, now i have used object(shape) based approach for each new line.
For fast undo : Just removed last shape from stage and called stage.update() to redraw all shapes.
For fast drawing: Just replaced stage.update() call with currentShape.draw(ctx) in my tick() listener or handleMouseMove() and it works like a charm. currentShape.draw() only draws shape on which it is called while stage.update() redraws all its children shapes too on each tick() making drawing slow.
Working fiddle for both fast undo and fast drawing http://jsfiddle.net/MkRCg/134/
var stage;
var isMouseDown;
var currentShape;
var oldMidX, oldMidY, oldX, oldY;
var canvas;
var ctx;
function init() {
stage = new createjs.Stage('canvas');
stage.autoClear = true;
stage.addEventListener("stagemousedown", handleMouseDown);
stage.addEventListener("stagemouseup", handleMouseUp);
createjs.Touch.enable(stage);
stage.update();
createjs.Ticker.addEventListener("tick", tick);
//bind element with id 'undo-drawing' with undoDrawing function
var undoEl = document.getElementById('undo-drawing');
undoEl.addEventListener("click",undoDrawing);
ctx = stage.canvas.getContext('2d');
}
function stop() {
createjs.Ticker.removeEventListener("tick", tick);
}
function tick() {
if (isMouseDown) {
var pt = new createjs.Point(stage.mouseX, stage.mouseY);
var midPoint = new createjs.Point(oldX + pt.x>>1, oldY+pt.y>>1);
currentShape.graphics.moveTo(midPoint.x, midPoint.y);
currentShape.graphics.curveTo(oldX, oldY, oldMidX, oldMidY);
oldX = pt.x;
oldY = pt.y;
oldMidX = midPoint.x;
oldMidY = midPoint.y;
/* Costly redrawing all previous objects too */
//stage.update();
/* only draws currentShape, made drawing fast*/
currentShape.draw(ctx);
}
}
function handleMouseDown() {
isMouseDown = true;
var s = new createjs.Shape();
oldX = stage.mouseX;
oldY = stage.mouseY;
oldMidX = stage.mouseX;
oldMidY = stage.mouseY;
var g = s.graphics;
var stroke = 2;
g.setStrokeStyle(stroke, 'round', 'round');
var color = "#000000";
g.beginStroke(color);
stage.addChild(s);
currentShape = s;
}
function handleMouseUp() {
isMouseDown = false;
//make drawing smooth on each mouse up
stage.update();
// alert();
}
function undoDrawing() {
console.log('undo clicked');
stage.removeChildAt(stage.children.length - 1);
stage.update();
}
init();
Upvotes: 0
Reputation: 56
I haven't tested this out on mobile with a large # of shapes yet but you could make the drawing canvas a Container, and each line its own Shape, and then in the case of undoing, you don't have to redraw anything, just remove the last shape from the canvas.
Upvotes: 1