This question already has an answer here:
HTML5 Canvas slows down with each stroke and clear
(1 answer)
Closed 10 months ago.
I am creating a web based drawing board. Part of the functionality of this is to draw shapes (like lines).
I'm finding myself needing to keep track of all the points drawn in the canvas in an array. Specifically because I need to clear the board and redraw the points when drawing lines and other shapes to make it feel "interactive". To clarify the functionality, I've created this JSFiddle https://jsfiddle.net/hwa95rcx/35/
HTML:
<canvas id="test" width="500" height="500">
</canvas>
JavasScript:
let lineStartX = 0;
let lineStartY = 0;
const drawingPoints = [];
let mouseDown = false;
const c = document.getElementById("test");
const ctx = c.getContext("2d");
c.onmousedown = (e) => {
lineStartX = e.clientX;
lineStartY = e.clientY;
mouseDown = true;
}
c.onmousemove = (e) => {
if (mouseDown) {
ctx.beginPath();
ctx.clearRect(0, 0, 500, 500);
redraw();
ctx.moveTo(lineStartX, lineStartY);
ctx.lineTo(e.clientX, e.clientY);
ctx.stroke();
}
}
c.onmouseup = (e) => {
mouseDown = false;
drawingPoints.push({
startX: lineStartX,
startY: lineStartY,
lineEndX: e.clientX,
lineEndY: e.clientY
});
}
function redraw() {
drawingPoints.forEach(el => {
ctx.moveTo(el.startX, el.startY);
ctx.lineTo(el.lineEndX, el.lineEndY);
ctx.stroke()
});
}
that exemplifies the reason I need to store an array of points to redraw.
I would like to avoid doing this because at large quantities of data, the drawing slows down or even crashes because we are looping through all the points to redraw them. Is there a more performant way of achieving this interactive shape drawing effect I am looking for?
Your redraw function calls stroke() for every sub-segment, but never calls beginPath(). This means that when you have two sub-segments, the first one will get painted at its own turn, and then again when the second sub-segment calls stroke().
You can see this quite clearly by looking at your first strokes and how their antialiasing darkens the more sub-segments there are.
You are actually asking the computer to render magnitudes more items than required, this is what is making your app slow and eventually crash.
So you could call beginPath() in the loop, but the best is instead to remove entirely stroke() from there. This way, at every frame you only paint one single path. The GPU has only one single operation to do and you should be fine for days of drawings.
let lineStartX = 0;
let lineStartY = 0;
const drawingPoints = [];
let mouseDown = false;
const c = document.getElementById("test");
const ctx = c.getContext("2d");
c.onmousedown = (e) => {
lineStartX = e.clientX;
lineStartY = e.clientY;
mouseDown = true;
}
c.onmousemove = (e) => {
if (mouseDown) {
ctx.beginPath();
ctx.clearRect(0, 0, 500, 500);
redraw();
ctx.moveTo(lineStartX, lineStartY);
ctx.lineTo(e.clientX, e.clientY);
ctx.stroke();
}
}
c.onmouseup = (e) => {
mouseDown = false;
drawingPoints.push({
startX: lineStartX,
startY: lineStartY,
lineEndX: e.clientX,
lineEndY: e.clientY
});
}
function redraw() {
// 'redraw' only traces the previous sub-segments
drawingPoints.forEach(el => {
ctx.moveTo(el.startX, el.startY);
ctx.lineTo(el.lineEndX, el.lineEndY);
});
}
<canvas id="test" width="500" height="500"></canvas>
Note that if you measure that even the tracing takes some computation time (though this would be surprising), you may want to try storing the sub-path in a Path2D object instead of calling the drawing methods every time:
let lineStartX = 0;
let lineStartY = 0;
const path = new Path2D();
let mouseDown = false;
const c = document.getElementById("test");
const ctx = c.getContext("2d");
c.onmousedown = (e) => {
lineStartX = e.clientX;
lineStartY = e.clientY;
mouseDown = true;
}
c.onmousemove = (e) => {
if (mouseDown) {
ctx.beginPath();
ctx.clearRect(0, 0, 500, 500);
redraw();
ctx.moveTo(lineStartX, lineStartY);
ctx.lineTo(e.clientX, e.clientY);
ctx.stroke();
}
}
c.onmouseup = (e) => {
mouseDown = false;
path.moveTo(lineStartX, lineStartY);
path.lineTo(e.clientX, e.clientY);
}
function redraw() {
ctx.stroke(path);
}
<canvas id="test" width="500" height="500"></canvas>
Related
I'm trying to do my Paint-like using HTML 5 Canvas. I have to do two type of rectangle: filled and not filled. Here's how I did that:
function rectangle(state = false) {
var first = [];
var second = [];
var click = 0;
canvas.mousedown(function(e) {
if (status == "rectangle") {
if (click == 0) {
first[0] = e.offsetX;
first[1] = e.offsetY;
click++;
} else {
second[0] = e.offsetX;
second[1] = e.offsetY;
if (state) {
ctx.fillRect(
first[0],
first[1],
second[0] - first[0],
second[1] - first[1]
);
} else {
ctx = canvas[0].getContext("2d");
ctx.strokeRect(
first[0],
first[1],
second[0] - first[0],
second[1] - first[1]
);
}
click = 0;
}
}
});
}
and this is my call
//rectangle ;)
$("#fill").click(function() {
$("#alert").text("fill Rectangle");
status = "rectangle";
rectangle(true);
});
$("#contour").click(function() {
$("#alert").text("Rectangle");
status = "rectangle";
rectangle();
});
I have 2 buttons for choosing which type of rectangle the user wants.
When I click on filled I can draw my rectangle like I want but when I switch to not filled, it still draws by filling the rectangle. For drawing a not filled rectangle I have to press F5 and click first in not filled.
Your problem is you're adding a new event listener each time the fill checkbox is modified, but not removing the old listener. This means you're drawing both the outline-rect and the filled-rect. Instead, only add a single event listener and handle state indide it.
Additional notes:
Firstly, you don't have to recreate a new context each click.
Secondly, you have a lot of repeated code
Thirdly, use {x, y} instead of [x, y] for future ease of modification.
let shape = 'rectangle'
let fill = false;
let clicks = [], clickCount = 0;
let fillCheck = document.querySelector('input');
fillCheck.addEventListener('input', () => fill = fillCheck.checked);
let canvas = document.querySelector('canvas');
let ctx = canvas.getContext("2d");
canvas.addEventListener('mousedown', e => {
if (shape == 'rectangle') {
clicks[clickCount++] = {x: e.offsetX, y: e.offsetY};
if (clickCount === 2) {
ctx[fill ? 'fillRect' : 'strokeRect'](
clicks[0].x,
clicks[0].y,
clicks[1].x - clicks[0].x,
clicks[1].y - clicks[0].y);
clickCount = 0;
}
}
});
canvas {
outline: 1px solid;
}
<div><label><input type="checkbox">Fill?</label></div>
<canvas width="300" height="300"></canvas>
I am working on a sketch tool with html canvas.
I am using a common algorithm for this, that uses the mousedown, mousemove, mouseup events.
mousedown
I beginPath(), and moveTo(// the mouse coordinates).
mousemove
I draw lineTo(// the mouse coordinates), and then stoke(// the line to render it)
mouseup
I do nothing, // closePath()
And I noticed that, calling the stroke method without first calling closePath or beginPath, will redraw or restroke all previous paths or lines, which makes them appear thicker than the define color.
without a transparent color its is barely noticeable, but the colors do appear thicker than they should be.
but with color with transparency|alpha e.g. rgba(). The most recent path or line respects the color's transparency, but all previous line due to the redraw, the transparent colored line overlap and that causes previous lines to get thicker in sequence or succession.
is there a way to avoid|prevent this behavior. thank in advance.
sample is below, try drawing really fast!
var cvs = document.querySelector("canvas");
cvs.width = cvs.parentElement.clientWidth;
var colorInput = document.querySelector("input");
var ctx = cvs.getContext("2d");
ctx.strokeStyle = "rgba(0, 0, 0, 0.4)"
ctx.lineWidth = 20;
onDraw(cvs, {
penDown: function(e) {
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
ctx.strokeStyle = colorInput.value;
ctx.beginPath();
ctx.moveTo(x, y);
},
penMove: function(e) {
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
ctx.lineTo(x, y);
ctx.stroke();
},
penUp: function() {
// ctx.closePath;
}
});
function onDraw(node, drawHandler, beginHandler, endHandler, outOfBoundHandler, sticky) {
var mouseDown = false, mouseOut = false;
if( typeof drawHandler === "object" ) {
var drawEvents = drawHandler;
drawHandler = get(drawEvents.penMove);
beginHandler = get(drawEvents.penDown);
endHandler = get(drawEvents.penUp);
outOfBoundHandler = get(drawEvents.penOff);
sticky = drawEvents.sticky;
}
function get(name) {
return typeof name === "string" ? drawEvents[ name ] : name;
}
node.addEventListener('mousedown', function(e) {
mouseDown = true;
beginHandler&&beginHandler.call(this, e);
});
node.addEventListener('mousemove', function(e) {
mouseDown&&drawHandler&&drawHandler.call(this, e);
});
node.addEventListener('mouseup', function(e) {
mouseDown = false;
endHandler&&endHandler.call(this, e);
});
node.addEventListener('mouseout', function(e) {
mouseDown&&outOfBoundHandler&&outOfBoundHandler.call(this, e);
if( !sticky ) {
mouseDown = false;
}
});
}
.wrapper { border: 1px solid #aaa }
<div class="wrapper">
<canvas border="1" width="600" hieght="400">Canvas is not supported</canvas>
<input type="text" value="rgba(0, 0, 0, 0.3)" placeholder="rgba(#, #, #, #)">
</div>
If no Path argument is passed to stroke and fill methods they will use the path currently being declared with the context's drawing methods.
const ctx = c.getContext('2d');
// starts Path declaration
ctx.moveTo(20, 20);
ctx.lineTo(30, 80);
ctx.stroke(); // first rendering
setTimeout(() => {
ctx.clearRect(0, 0, 300, 150); // even if we clear the canvas
ctx.lineTo(70, 20); // this will continue path declaration
setTimeout(() => {
ctx.stroke(); // and this will draw everything
}, 1000);
}, 1000);
<canvas id="c"></canvas>
The only ways to start a new path declaration (except for the first one) are to either reset the whole context (not good), or to use beginPath method.
const ctx = c.getContext('2d');
// starts Path declaration
ctx.moveTo(20, 20);
ctx.lineTo(30, 80);
ctx.stroke(); // first rendering
setTimeout(() => {
ctx.clearRect(0, 0, 300, 150);
ctx.beginPath(); // start a new Path declaration
ctx.moveTo(30, 80); // we need to move to the previous coords
ctx.lineTo(70, 20); // this will be alone
ctx.stroke(); // and this will draw only the new path
}, 1000);
<canvas id="c"></canvas>
About closePath, it's just a lineTo(last_point_in_current_path_declaration), and doesn't ends a path declaration in no way.
So for your problem, there are two strategies you can adopt :
keep only the last coordinates, and at every mousemove,
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(nextX, nextY);
keep all your coordinates in an array and redraw everything every time
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
coords.forEach(pt => ctx.lineTo(pt.x, pt.y));
ctx.stroke();
Personally, I prefer the second one, which allows some undo - redo, and to e.g change your pen's style.
I am trying to create a pannable image viewer which also allows magnification. If the zoom factor or the image size is such that the image no longer paints over the entire canvas then I wish to have the area of the canvas which does not contain the image painted with a specified background color.
My current implementation allows for zooming and panning but with the unwanted effect that the image leaves a tiled trail after it during a pan operation (much like the cards in windows Solitaire when you win a game). How do I clean up my canvas such that the image does not leave a trail and my background rectangle properly renders in my canvas?
To recreate the unwanted effect set magnification to some level at which you see the dark gray background show and then pan the image with the mouse (mouse down and drag).
Code snippet added below and Plnkr link for those who wish to muck about there.
http://plnkr.co/edit/Cl4T4d13AgPpaDFzhsq1
<!DOCTYPE html>
<html>
<head>
<style>
canvas{
border:solid 5px #333;
}
</style>
</head>
<body>
<button onclick="changeScale(0.10)">+</button>
<button onclick="changeScale(-0.10)">-</button>
<div id="container">
<canvas width="700" height="500" id ="canvas1"></canvas>
</div>
<script>
var canvas = document.getElementById('canvas1');
var context = canvas.getContext("2d");
var imageDimensions ={width:0,height:0};
var photo = new Image();
var isDown = false;
var startCoords = [];
var last = [0, 0];
var windowWidth = canvas.width;
var windowHeight = canvas.height;
var scale=1;
photo.addEventListener('load', eventPhotoLoaded , false);
photo.src = "http://www.html5rocks.com/static/images/cors_server_flowchart.png";
function eventPhotoLoaded(e) {
imageDimensions.width = photo.width;
imageDimensions.height = photo.height;
drawScreen();
}
function changeScale(delta){
scale += delta;
drawScreen();
}
function drawScreen(){
context.fillRect(0,0, windowWidth, windowHeight);
context.fillStyle="#333333";
context.drawImage(photo,0,0,imageDimensions.width*scale,imageDimensions.height*scale);
}
canvas.onmousedown = function(e) {
isDown = true;
startCoords = [
e.offsetX - last[0],
e.offsetY - last[1]
];
};
canvas.onmouseup = function(e) {
isDown = false;
last = [
e.offsetX - startCoords[0], // set last coordinates
e.offsetY - startCoords[1]
];
};
canvas.onmousemove = function(e)
{
if(!isDown) return;
var x = e.offsetX;
var y = e.offsetY;
context.setTransform(1, 0, 0, 1,
x - startCoords[0], y - startCoords[1]);
drawScreen();
}
</script>
</body>
</html>
You need to reset the transform.
Add context.setTransform(1,0,0,1,0,0); just before you clear the canvas and that will fix your problem. It sets the current transform to the default value. Then befor the image is draw set the transform for the image.
UPDATE:
When interacting with user input such as mouse or touch events it should be handled independently of rendering. The rendering will fire only once per frame and make visual changes for any mouse changes that happened during the previous refresh interval. No rendering is done if not needed.
Dont use save and restore if you don't need to.
var canvas = document.getElementById('canvas1');
var ctx = canvas.getContext("2d");
var photo = new Image();
var mouse = {}
mouse.lastY = mouse.lastX = mouse.y = mouse.x = 0;
mouse.down = false;
var changed = true;
var scale = 1;
var imageX = 0;
var imageY = 0;
photo.src = "http://www.html5rocks.com/static/images/cors_server_flowchart.png";
function changeScale(delta){
scale += delta;
changed = true;
}
// Turns mouse button of when moving out to prevent mouse button locking if you have other mouse event handlers.
function mouseEvents(event){ // do it all in one function
if(event.type === "mouseup" || event.type === "mouseout"){
mouse.down = false;
changed = true;
}else
if(event.type === "mousedown"){
mouse.down = true;
}
mouse.x = event.offsetX;
mouse.y = event.offsetY;
if(mouse.down) {
changed = true;
}
}
canvas.addEventListener("mousemove",mouseEvents);
canvas.addEventListener("mouseup",mouseEvents);
canvas.addEventListener("mouseout",mouseEvents);
canvas.addEventListener("mousedown",mouseEvents);
function update(){
requestAnimationFrame(update);
if(photo.complete && changed){
ctx.setTransform(1,0,0,1,0,0);
ctx.fillStyle="#333";
ctx.fillRect(0,0, canvas.width, canvas.height);
if(mouse.down){
imageX += mouse.x - mouse.lastX;
imageY += mouse.y - mouse.lastY;
}
ctx.setTransform(scale, 0, 0, scale, imageX,imageY);
ctx.drawImage(photo,0,0);
changed = false;
}
mouse.lastX = mouse.x
mouse.lastY = mouse.y
}
requestAnimationFrame(update);
canvas{
border:solid 5px #333;
}
<button onclick="changeScale(0.10)">+</button><button onclick="changeScale(-0.10)">-</button>
<canvas width="700" height="500" id ="canvas1"></canvas>
Nice Code ;)
You are seeing the 'tiled' effect in your demonstration because you are painting the scaled image to the canvas on top of itself each time the drawScreen() function is called while dragging. You can rectify this in two simple steps.
First, you need to clear the canvas between calls to drawScreen() and second, you need to use the canvas context.save() and context.restore() methods to cleanly reset the canvas transform matrix between calls to drawScreen().
Given your code as is stands:
Create a function to clear the canvas. e.g.
function clearCanvas() {
context.clearRect(0, 0, canvas.width, canvas.height);
}
In the canavs.onmousemove() function, call clearCanvas() and invoke context.save() before redefining the transform matrix...
canvas.onmousemove = function(e) {
if(!isDown) return;
var x = e.offsetX;
var y = e.offsetY;
/* !!! */
clearCanvas();
context.save();
context.setTransform(
1, 0, 0, 1,
x - startCoords[0], y - startCoords[1]
);
drawScreen();
}
... then conditionally invoke context.restore() at the end of drawScreen() ...
function drawScreen() {
context.fillRect(0,0, windowWidth, windowHeight);
context.fillStyle="#333333";
context.drawImage(photo,0,0,imageDimensions.width*scale,imageDimensions.height*scale);
/* !!! */
if (isDown) context.restore();
}
Additionally, you may want to call clearCanvas() before rescaling the image, and the canvas background could be styled with CSS rather than .fillRect() (in drawScreen()) - which could give a performance gain on low spec devices.
Edited in light of comments from Blindman67 below
See Also
Canvas.context.save : https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/save
Canvas.context.restore : https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/restore
requestAnimationFrame : https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame
Paul Irish, requestAnimationFrame polyfill : http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
Call context.save to save the transformation matrix before you call context.fillRect.
Then whenever you need to draw your image, call context.restore to restore the matrix.
For example:
function drawScreen(){
context.save();
context.fillStyle="#333333";
context.fillRect(0,0, windowWidth, windowHeight);
context.restore();
context.drawImage(photo,0,0,imageDimensions.width*scale,imageDimensions.height*scale);
}
Also, to further optimize, you only need to set fillStyle once until you change the size of canvas.
Iām trying to use a drop down menu to change the shape of a brush from round to square in a paint program using Canvas.
Here is what I have so far in this Fiddle.
https://jsfiddle.net/ohdust/k7wzj3ww/2/
var tool = false;
var toolDefault = 'rect';
var toolSelect = document.getElementById('dtool');
I'm not sure how to go at this. Any examples would be helpful.
I've tried searching around but have not had any luck.
Define a function for each pencil types, for example:
function setRound() {
ctx.lineWidth = 5;
ctx.lineCap = 'round';
ctx.lineJoin = "round";
ctx.strokeStyle = '#2b39c0';
}
function setSquare() {
ctx.lineWidth = 5;
ctx.lineCap = 'butt';
ctx.lineJoin = "miter";
ctx.strokeStyle = '#c0392b';
}
You would also have to remove setting the stroke style from the mouse handlers (see fiddle for additions to the resize handler as well as the currentTool declaration used below).
(if you have many different styles I would suggest considering at least an array and custom pen objects).
Then use a switch selector when an event on the tool selector is triggered:
toolSelect.addEventListener('change', setPencil);
...
function setPencil() {
switch(this.value) {
case "rect":
currentTool = setSquare; break;
case "pencil":
currentTool = setRound; break;
}
currentTool();
}
Now the pencil will be updated according to the selected pencil in the menu.
Additionally, mouse position needs to be corrected - just add this to adjust:
function setPosition(e) {
var rect = canvas.getBoundingClientRect();
pos.x = e.clientX - rect.left;
pos.y = e.clientY - rect.top;
}
Updated fiddle
I have a small feeling you maybe ask for how to draw a rectangle and not a squared tip line. If so, check out this answer.
I used the onchange attribute to run a function to change the type of brush. There is no rectangle shape for a brush as of now. See here.
Note: Other improvements need to be done in your code. For example the drawing is occurring far away from the cursor. And on click and move the browser is trying to drag things by default. So I guess you have to add a mousemove function to the canvas with event.preventDefault().
var canvas = document.createElement('canvas');
document.body.appendChild(canvas);
var toolSelect = document.getElementById('dtool');
var brush = {};
brush.shape = 'round';
brush.size = 1;
function setBrush(type) {
switch (type) {
case 'pencil':
brush.shape = 'round';
brush.size = 1;
break;
case 'square':
brush.shape = 'square';
brush.size = 10;
break;
}
}
// some hotfixes... ( ā_ā)
document.body.style.margin = 0;
canvas.style.position = 'fixed';
// get canvas 2D context and set him correct size
var ctx = canvas.getContext('2d');
resize();
// last known position
var pos = {
x: 0,
y: 0
};
window.addEventListener('resize', resize);
document.addEventListener('mousemove', draw);
document.addEventListener('mousedown', setPosition);
document.addEventListener('mouseenter', setPosition);
// new position from mouse event
function setPosition(e) {
pos.x = e.clientX;
pos.y = e.clientY;
}
// resize canvas
function resize() {
ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
}
function draw(e) {
// mouse left button must be pressed
if (e.buttons !== 1) return;
ctx.beginPath(); // begin
ctx.lineWidth = brush.size;
ctx.lineCap = brush.shape;
ctx.strokeStyle = '#c0392b';
ctx.moveTo(pos.x, pos.y); // from
setPosition(e);
ctx.lineTo(pos.x, pos.y); // to
ctx.stroke(); // draw it!
}
<label>Drawing tool:
<select id="dtool" onchange="setBrush(this.value)">
<option value="">Select</option>
<option value="square">Square</option>
<option value="pencil">Pencil</option>
</select>
</label>
Hello i create program like a paint on HTML5 canvas. I have problem i need create few tools drawing and zoom. I don't have idea how to create zoom without delay. Drawing example: http://jsfiddle.net/x5rrvcr0/
How i can zooming my drawings?
drawing code:
<style>
canvas {
background-color: #CECECE;
}
html, body {
background-color: #FFFFFF;
}
</style>
<script>
$(document).ready(function () {
var paintCanvas = document.getElementById("paintCanvas");
var paintCtx = paintCanvas.getContext("2d");
var size = 500;
paintCanvas.width = size;
paintCanvas.height = size;
var draw = false;
var prevMouseX = 0;
var prevMouseY = 0;
function getMousePos(canvas, evt) {
evt = evt.originalEvent || window.event || evt;
var rect = canvas.getBoundingClientRect();
if (evt.clientX !== undefined && evt.clientY !== undefined) {
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
}
$("#paintCanvas").on("mousedown", function(e) {
draw = true;
var coords = getMousePos(paintCanvas);
prevMouseX = coords.x;
prevMouseY = coords.y;
});
$("#paintCanvas").on("mousemove", function(e) {
if(draw) {
var coords = getMousePos(paintCanvas, e);
paintCtx.beginPath();
paintCtx.lineWidth = 10;
paintCtx.strokeStyle = "#000000";
paintCtx.moveTo(prevMouseX, prevMouseY);
paintCtx.lineTo(coords.x, coords.y);
paintCtx.stroke();
prevMouseX = coords.x;
prevMouseY = coords.y;
}
});
$("#paintCanvas").on("mouseup", function(e) {
draw = false;
});
});
</script>
<body>
<canvas id="paintCanvas"></canvas>
</body>
If you want to keep the pixelated effect in the zoom, you need to draw on a temp canvas, then only after copy that temp canvas to the main screen.
You no longer need to zoom in the temp canvas, just draw on 1:1 scale always. When copying to the view canvas, then you apply the zoom (and maybe translate) that you want.
Keep in mind that drawings are anti-aliased, so you when zooming you will see some shades of grey when drawing in black, for instance.
I kept the recording code of #FurqanZafar since it is a good idea to record things in case you want to perform undo : in that case just delete the last record entry and redraw everything.
http://jsfiddle.net/gamealchemist/x5rrvcr0/4/
function updatePaintCanvas() {
paintContext.clearRect(0, 0, paintContext.canvas.width, paintContext.canvas.height);
paintContext.save();
paintContext.translate(cvSize * 0.5, cvSize * 0.5);
paintContext.scale(scale, scale);
paintContext.drawImage(drawCanvas, -cvSize * 0.5, -cvSize * 0.5);
paintContext.restore();
}
Heres the updated fiddle: http://jsfiddle.net/x5rrvcr0/2/ with basic zooming functionality
If you draw multiple paths on mouse move then your sketch will appear broken or disconnected, instead you should only stroke a single path until "mouseup" event.
You can then store these paths in an array and later redraw them at different zoom levels:
function zoom(context, paths, styles, scale) {
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
context.save();
applyStyles(context, styles);
scaleFromCenter(context, scale);
for (var i = 0; i < paths.length; i++) {
context.beginPath();
context.moveTo(paths[i][0].x, paths[i][0].y);
for (var j = 1; j < paths[i].length; j++)
context.lineTo(paths[i][j].x, paths[i][j].y);
context.stroke();
}
context.restore();
};