Smooth user drawn lines in canvas - html

I'm using <canvas> to capture user input in the form of a signature and am trying to figure out how to smooth the input from the mouse.
I think I need to process the user's mouse movements chunk by chunk and smooth each chunk, I'm not after super smoothing but any improvement on the jagged input would be good.
Thanks,
Mark

What you want is:
ctx.lineCap = 'round';
Here is an example of how it could be used:
Give it a try http://jsbin.com/ateho3
markup :
<canvas id="canvas"></canvas>
JavaScript :
window.onload = function() {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var width = window.innerWidth;
var height = window.innerHeight;
canvas.height = height;
canvas.width = width;
canvas.addEventListener('mousedown', function(e) {
this.down = true;
this.X = e.pageX ;
this.Y = e.pageY ;
this.color = rgb();
}, 0);
canvas.addEventListener('mouseup', function() {
this.down = false;
}, 0);
canvas.addEventListener('mousemove', function(e) {
this.style.cursor = 'pointer';
if(this.down) {
ctx.beginPath();
ctx.moveTo(this.X, this.Y);
ctx.lineCap = 'round';
ctx.lineWidth = 3;
ctx.lineTo(e.pageX , e.pageY );
ctx.strokeStyle = this.color;
ctx.stroke();
this.X = e.pageX ;
this.Y = e.pageY ;
}
}, 0);
function rgb() {
color = 'rgb(';
for(var i = 0; i< 3; i++) {
color += Math.floor(Math.random() * 255)+',';
}
return color.replace(/\,$/,')');
}
};

I know that this is a 10 years old question but I think the answer is not complete. For a smooth line effect you need to set two properties to the canvas' context :
context.lineCap = 'round'
context.lineJoin = 'round'
The first one is for extremities of one path the second one is for corners of a path.
Some docs on lineJoin.
Some docs on lineCap.

I had to make a smooth canvas drawing for an mobile web application and learned couple of things.
The Answer of Avinash is great but if you increase the line width, when you draw you will see broken lines. It is because the line cap is rectangular by default.
To make the line smoother you need to tweak something a bit.
ctx.lineCap = 'round';
this little tweak will give you a super smooth line.
To know more about this, try the following link
https://developer.mozilla.org/samples/canvas-tutorial/4_6_canvas_linecap.html

How about using Bezier curves?

I haven't tested this in any way, but you could try drawing small circles with a radial fill gradient.

Consider connecting dots by using lines automatically, or even, use quadraticCurveTo, but you must calculate the middle point by yourself.

Related

HTML5 Canvas Sweep Gradient

It looks like the HTML5 canvas does not support a "sweep gradient" - a gradient where the color stops rotate around the center, rather than emanating from the center.
Is there any way to simulate a sweep gradient on a canvas? I suppose I could do something similar with lots of little linear gradients, but at that point I'm basically rendering the gradient myself.
Indeed there is no built-in for such a thing.
Not sure what you had in mind with these "lots of little linear gradients", but you actually just need a single one, the size of your circle's circumference, and only to get the correct colors to use.
What you'll need a lot though are lines, since we'll draw these around the center point using the solid colors we had in the linearGradient.
So to render this, you just move to the center point, then draw a line using a solid color from the linear gradient, then rotate and repeat.
To get all the colors of a linearGradient, you just need to draw it and map it's ImageData to CSS colors.
The hard part though is that to be able to have an object that behaves like a CanvasGradient, we need to be able to set it as a fillStyle or strokeStyle.
This is possible by returning a CanvasPattern. An other difficulty is that gradients are virtually infinitely big. A non-repeating Pattern is not.
I didn't found a good solution to overcome this problem, but as a workaround, we can use the size of the target canvas as a limit.
Here is a rough implementation:
class SweepGrad {
constructor(ctx, x, y) {
this.x = x;
this.y = y;
this.target = ctx;
this.colorStops = [];
}
addColorStop(offset, color) {
this.colorStops.push({offset, color});
}
render() {
// get the current size of the target context
const w = this.target.canvas.width;
const h = this.target.canvas.width;
const x = this.x;
const y = this.y;
// get the max length our lines can be
const maxDist = Math.ceil(Math.max(
Math.hypot(x, y),
Math.hypot(x - w, y),
Math.hypot(x - w, y - h),
Math.hypot(x, y - h)
));
// the circumference of our maxDist circle
// this will determine the number of lines we will draw
// (we double it to avoid some antialiasing artifacts at the edges)
const circ = maxDist*Math.PI*2 *2;
// create a copy of the target canvas
const canvas = this.target.canvas.cloneNode();
const ctx = canvas.getContext('2d');
// generate the linear gradient used to get all our colors
const linearGrad = ctx.createLinearGradient(0, 0, circ, 0);
this.colorStops.forEach(stop =>
linearGrad.addColorStop(stop.offset, stop.color)
);
const colors = getLinearGradientColors(linearGrad, circ);
// draw our gradient
ctx.setTransform(1,0,0,1,x,y);
for(let i = 0; i<colors.length; i++) {
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(maxDist, 0);
ctx.strokeStyle = colors[i];
ctx.stroke();
ctx.rotate((Math.PI*2)/colors.length);
}
// return a Pattern so we can use it as fillStyle or strokeStyle
return ctx.createPattern(canvas, 'no-repeat');
}
}
// returns an array of CSS colors from a linear gradient
function getLinearGradientColors(grad, length) {
const canvas = Object.assign(document.createElement('canvas'), {width: length, height: 10});
const ctx = canvas.getContext('2d');
ctx.fillStyle = grad;
ctx.fillRect(0,0,length, 10);
return ctx.getImageData(0,0,length,1).data
.reduce((out, channel, i) => {
const px_index = Math.floor(i/4);
const px_slot = out[px_index] || (out[px_index] = []);
px_slot.push(channel);
if(px_slot.length === 4) {
px_slot[3] /= 255;
out[px_index] = `rgba(${px_slot.join()})`;
}
return out;
}, []);
}
// How to use
const ctx = canvas.getContext('2d');
const redblue = new SweepGrad(ctx, 70, 70);
redblue.addColorStop(0, 'red');
redblue.addColorStop(1, 'blue');
// remeber to call 'render()' to get the Pattern back
// maybe a Proxy could handle that for us?
ctx.fillStyle = redblue.render();
ctx.beginPath();
ctx.arc(70,70,50,Math.PI*2,0);
ctx.fill();
const yellowgreenred = new SweepGrad(ctx, 290, 80);
yellowgreenred.addColorStop(0, 'yellow');
yellowgreenred.addColorStop(0.5, 'green');
yellowgreenred.addColorStop(1, 'red');
ctx.fillStyle = yellowgreenred.render();
ctx.fillRect(220,10,140,140);
// just like with gradients,
// we need to translate the context so it follows our drawing
ctx.setTransform(1,0,0,1,-220,-10);
ctx.lineWidth = 10;
ctx.strokeStyle = ctx.fillStyle;
ctx.stroke(); // stroke the circle
canvas{border:1px solid}
<canvas id="canvas" width="380" height="160"></canvas>
But beware, all this is quite computationally heavy, so be sure to use it sporadically and to cache your resulting Gradients/Patterns.

ctx.store() redrawing the previous points also [duplicate]

I have a web application where you can draw a rectangle on a canvas. I use two canvas elements: one for the preview while drawing and another one laying exactly under the other one for drawing it.
The problem I have is that in Internet Explorer, canvas2.width = canvas2.width doesn't clear the content of canvas2, which is necessary because for every mousemove the rectangle gets drawn again. I also tried context2.clearRect(0,0,canvas2.width,canvas2.height), but, however, then the preview rectangle doesn't get drawn at all. Try it out on http://jsfiddle.net/Y389a/2/
HTML:
<canvas id="canvas" width="600" height="400"></canvas>
<canvas id="canvas2" width="600" height="400" onmouseup="return drawLine()" onmousedown="return startLine()"></canvas>
CSS:
#canvas, #canvas2 {
position:absolute;
left:0px;
top:0px;
border-width:1px;
border-style:solid;
border-color:#666666;
cursor:default !important;
}
Javascript:
var x; var xStart;
var y; var yStart;
var clicked = false;
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var canvas2 = document.getElementById("canvas2");
var context2 = canvas2.getContext("2d");
context.strokeStyle = "black";
context.lineCap = "round";
canvas2.addEventListener('mousemove', function (evt) {
var rect = canvas2.getBoundingClientRect();
x = evt.clientX - rect.left;
y = evt.clientY - rect.top;
if (clicked) {
canvas2.width = canvas2.width;
context2.rect(xStart, yStart, x - xStart, y - yStart);
context2.stroke();
}
}, false);
function startLine() {
context.beginPath();
xStart = x; yStart = y;
clicked = true;
}
function drawLine() {
clicked = false;
context.rect(xStart, yStart, x - xStart, y - yStart);
context.stroke();
}
Preview
Problem
You are drawing rectangles with context2.rect which is a path command.
Path commands are "remembered" by the canvas until a new context2.beginPath is issued
Therefore, all your previous rects are being remembered and redrawn when you do context2.stroke
Fix
Just put context2.beginPath in your mousemove event handler: http://jsfiddle.net/m1erickson/A8ge6/
canvas2.addEventListener("mousedown",startLine);
canvas2.addEventListener("mouseup",drawLine);
canvas2.addEventListener('mousemove', function (evt) {
var rect = canvas2.getBoundingClientRect();
x = evt.clientX - rect.left;
y = evt.clientY - rect.top;
if (clicked) {
canvas2.width = canvas2.width;
console.log(xStart);
// add beginPath so previous context2.rect's are dismissed
context2.beginPath();
context2.rect(xStart, yStart, x - xStart, y - yStart);
context2.stroke();
}
}, false);
If you only need to stroke a rectangle you can use this version:
context2.strokeRect(xStart, yStart, x - xStart, y - yStart);
instead of rect() + stroke().
This does not add any sub path to the main path but draws directly to canvas. If you need to add other shapes to your path later remember to use beginPath() for rect() in a similar way as you already do in startLine() as rect() add a sub-path.
There is Nothing Wrong with the Code and nothing Wrong With IE 9,What you missed is a l'le concept ,
addEventListener() didn't work For IE instead you have to use attachEvent() for it to make your Code run in IE
//For your code to work in IE
if (!canvas2.addEventListener) {
canvas2.attachEvent("onclick", CanvasFunction);
}
//for rest of the Browser
else {
canvas2.addEventListener("click", CanvasFunction, false);
}
function CanvasFunction(evt)
{
var rect = canvas2.getBoundingClientRect();
x = evt.clientX - rect.left;
y = evt.clientY - rect.top;
if (clicked) {
canvas2.width = canvas2.width;
console.log(xStart);
// add beginPath so previous context2.rect's are dismissed
context2.beginPath();
context2.rect(xStart, yStart, x - xStart, y - yStart);
context2.stroke();
}
}
Playing with Canvas ,remember IE doesn't support addEventListners ..Enjoy Coding

How can I fill in the outside of a path?

I am able to draw these letters using a path. But what I want to do is use that path and fill in what the red image shows instead of filling in the letters.
Here is the code I am using:
function mattes_draw_letter(x, y, width, height, letter, position)
{
var canvas = document.createElement('canvas');
canvas.style.position = "absolute";
canvas.style.top = y + "px";
canvas.id = "canvas_opening_" + position;
canvas.style.zIndex = 5;
canvas.width = width;
canvas.height = height;
canvas.style.left = x + "px";
var ctx = canvas.getContext("2d");
ctx.lineWidth = 1;
ctx.fillStyle = '#bfbfbf';
ctx.strokeStyle = '#000000';
ctx.beginPath();
ctx.moveTo(letter[0] * width, letter[1] * height);
for (i = 0; i < letter.length; i+=2)
{
if (typeof letter[i+3] !== 'undefined')
{
ctx.lineTo(letter[i+2] * width, letter[i+3] * height);
}
}
ctx.fill();
ctx.stroke();
ctx.closePath();
$("#mattes").append(canvas);
canvas.addEventListener("drop", function(event) {drop(event, this);}, false);
canvas.addEventListener("dragover", function(event) {allowDrop(event);}, false);
canvas.addEventListener("click", function() {photos_add_selected_fid(this);}, false);
}
This is what I currently have:
This is what I would like:
Just fill the boxes with red color before drawing the letters in gray.
I was able to do this by adding two lines of code in your code.
ctx.fillStyle = "#F00";
ctx.fillRect(0, 0, width, height);
Put these two lines between the lines:
ctx.lineWidth = 1;
and
ctx.fillStyle = '#bfbfbf';
I assume you're starting the existing letters otherwise (as #Chirag64 says), you can just draw the red rectangles first and then draw the letters on top).
You can use canvas compositing to "draw behind" existing content.
A Demo: http://jsfiddle.net/m1erickson/695dY/
In particular the destination-over compositing mode will draw new content behind existing content (new content is only drawn where the existing content is transparent).
context.globalCompositeOperation="destination-over";
Assuming the HOPE characters are drawn over a transparent background you can add red rectangles behind the HOPE characters like this:
// draw red rectangles **behind** the letters using compositing
ctx.fillStyle="red";
ctx.globalCompositeOperation="destination-over";
for(var i=0;i<4;i++){
ctx.fillRect(i*62+16,13,50,88); // your x,y,width,height depend on your artwork
}

Thorn of text outline in canvas

I'm using html5 canvas to draw some texts, but I got some ugly results, here is my sample code to draw the text:
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var x = 80;
var y = 110;
context.fillStyle = "white"
context.font = '13px Arial';
context.lineWidth = 3;
// stroke color
context.strokeStyle = 'black';
context.strokeText('ABCDEFGHIJKLMNOPQRSTUVWXYZ!', x, y);
context.fillText('ABCDEFGHIJKLMNOPQRSTUVWXYZ!', x, y);
And I got this result
then I change the text to "abs" and got this
You can see the "M" and "s" looks ugly, does anyone have an idea on how to solve this?
It's not due to the typeface nor size, but due to how the lines in the stroke path are connected.
By changing the line-join method the spikes will go away:
context.lineJoin = 'round';
Also, if you prefer sharp corners (without the miter-spikes,) try this:
context.lineJoin = 'miter';
context.miterLimit = 2;
http://jsfiddle.net/hwG42/3/
It does the trick.
You might get better results when you use a different font.
Alternatively, you could use only fillText and create the outline with a shadow.

How do I resize a path already closed on an HTML5 canvas?

I have a quadratic curve rendered on a canvas. I want to animate it by means of window.setInterval and changing it's dimensions (note not simply changing it's scale) thereafter.
How do I retain an editable reference to the path after calling context.closePath()?
I'd recommend that you maintained a reference to the path in a new Path object; that way you could modify x, y, points etc on the fly and then render it each animation step.
var testPath = new Path(100, 100, [[40, 40], [80, 80]]);
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
function Path(x, y, points)
{
this.x = x;
this.y = y;
this.points = points;
}
function update()
{
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = 'red';
ctx.moveTo(testPath.points[0][0], testPath.points[0][1]);
for (var i = 1; i < testPath.points.length; i++)
{
ctx.lineTo(testPath.points[i][0], testPath.points[i][1]);
}
ctx.stroke();
testPath.points[1][1]++; // move down
// loop
requestAnimationFrame(update);
}
update();​
For some reason JSFiddle doesn't play nice with Paul Irish's requestAnimationFrame polyfill but it should work locally. I'd definitely recommend this over setInterval.
http://jsfiddle.net/d2sSg/1/