Finding points on curves in HTML 5 2d Canvas context - html

Given lines drawn by 2d canvas context functions bezierCurveTo, quadraticCurveTo or arcTo, how can I find points along those lines?
My intent is to draw an object at the midpoint of a curve. Using the SVG DOM, I can do this with methods getPointAtLength & getTotalLength, but I can't see an equivalent in HTML canvas.

You find them the hard way :(
There isn't an equivalent in HTML canvas. You have to find the midpoints yourself with plain old math.
I did an example of how to find midpoint of bezier curves for you. See it live at jsFiddle here. A copy of the javascript is pasted below.
Real curve is red, midpoint is the tiny green rectangle. Everything else is just a visual aid.
var ctx = $("#test")[0].getContext("2d")
function mid(a,b) {
return (a+b) / 2;
}
var cp1x = 100;
var cp1y = 150;
var cp2x = 175;
var cp2y = 175;
var x = 200;
var y = 0;
ctx.lineWidth = 4;
ctx.strokeStyle = "red";
ctx.fillStyle = "rgba(0,0,0,0.6)";
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
ctx.stroke();
//line goes from start to control point 1
ctx.strokeStyle = "rgba(0,0,200,0.4)";
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(cp1x , cp1y);
ctx.stroke();
//line goes from end to control point 2
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(cp2x , cp2y);
ctx.stroke();
//line goes from control point to control point
ctx.strokeStyle = "rgba(200,0,200,0.4)";
ctx.beginPath();
ctx.moveTo(cp1x, cp1y);
ctx.lineTo(cp2x , cp2y);
ctx.stroke();
// now find the midpoint of each of those 3 lines
var ax = mid(cp1x,0);
var bx = mid(cp2x,x)
var cx = mid(cp1x,cp2x)
var ay = mid(cp1y,0)
var by = mid(cp2y,y)
var cy = mid(cp1y,cp2y)
// draw midpoints for visual aid
// not gonna look exact 'cause square
// will be drawn from top-right instead of center
ctx.fillRect(ax, ay, 4, 4);
ctx.fillRect(bx, by, 4, 4);
ctx.fillRect(cx, cy, 4, 4);
//now draw lines between those three points. These are green
ctx.strokeStyle = "rgba(0,200,0,0.4)";
ctx.beginPath();
ctx.moveTo(ax, ay);
ctx.lineTo(cx , cy);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(bx, by);
ctx.lineTo(cx , cy);
ctx.stroke();
//now the midpoint of the green lines:
// so g1 and g2 are the green line midpoints
var g1x = mid(ax,cx);
var g2x = mid(bx,cx);
var g1y = mid(ay,cy);
var g2y = mid(by,cy);
//draw them to make sure:
ctx.fillRect(g1x , g1y, 4, 4);
ctx.fillRect(g2x , g2y, 4, 4);
//now one final line, in gray
ctx.strokeStyle = "rgba(20,20,20,0.4)";
ctx.beginPath();
ctx.moveTo(g1x , g1y);
ctx.lineTo(g2x , g2y);
ctx.stroke();
//whew! We made it!
var FinallyTheMidpointx = mid(g1x,g2x);
var FinallyTheMidpointy = mid(g1y,g2y);
//draw something at the midpoint to celebrate
ctx.fillStyle = "rgba(0,255,0,1)";
ctx.fillRect(FinallyTheMidpointx, FinallyTheMidpointy, 4, 4);
​

A Bezier Curve is calculated Mathematically with this formula
Where P0 is the startpoint, P1 and P2 are the control points and P3 is the end point.
To calculate the halfwaypoint you can just use t = 0.5.
Similar for the Quadratic Curve:
Source and further Information

Check out http://pomax.github.io/bezierjs/
There's a bunch of handy functions there, the one you want to use is .get(t) where t=0.5

Related

HTM5 draw a shape from multiple objects and then fill it and set opacity

For example lets say I am drawing some circle with arrow, transformatting it and rotating (code and jsfiddle below)... In given code, arrow is red... but when I make it gradient, the gradient is being rotaded during creation of whole object... so I need to apply gradient after everything is drawn... how do I do that?
Second thing... when applying global alpha opacity to context before drawing, arrow on circle is darker, because there are 2 layers in same place... how do I apply opacity to whole shape?
JSFiddle here
var x1 = 10;
var y1 = 10;
var x2 = 50;
var y2 = 50;
var dx = x2 - x1;
var dy = y2 - y1;
var radians = Math.atan2(dy, dx);
var length = Math.sqrt(dx * dx + dy * dy);
ctx.save();
ctx.translate(x1, y1);
ctx.rotate(radians);
ctx.beginPath();
ctx.arc(0, 0, 8, 0, Math.PI * 2);
ctx.closePath();
ctx.fillStyle = "red";
ctx.fill();
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(length, 0);
ctx.lineTo(length - 7, -4);
ctx.lineTo(length - 7, 4);
ctx.lineTo(length, 0);
ctx.closePath();
ctx.fillStyle = "red";
ctx.fill();
ctx.strokeStyle = "red";
ctx.stroke();
ctx.restore();
Gradient rotation
When you createLinearGradient you specify the starting and ending xy's
(basically you specify the line along which the gradient will follow).
Therefore, you can adjust the angle of that line such that the gradient fits your design needs even after it's rotated.
Opacity of the combined circle+arrow
As you've discovered, if you overlap drawings then the resulting opacity will be darker on the overlap.
You have 2 solutions to the darkened opacity:
start drawing the arrow at on the circumference of the circle rather than staring the arrow at the circle's center. This way you have small or no overlap.
combine the arc and arrow into 1 single path.

multicolors in canvas LineTo

function TrackGraphic(model, canvas) {
//TrackModel
this._model = model;
this.draw = function(context) {
var dx = Math.cos(this._model.startAngle + Math.PI / 2);
var dy = Math.sin(this._model.startAngle + Math.PI / 2);
context.beginPath();
context.lineWidth = 10;
context.moveTo(this._model.offsetX, this._model.offsetY);
//CurvePoint
var p;
for (var d = 0; d < this._model.length; d += 1) {
if (d > 1000) {
console.log('2F2F2F');
context.strokeStyle = "#2F2F2F" //"rgb(255,165,0)"; //0x2F2F2F
} else {
context.strokeStyle = "#FFF" //"rgb(255,165,0)"; //0x2F2F2F;
console.log('FFFFFF');
}
p = this._model.getTrackPoint(d);
context.lineTo(this._model.offsetX + p.x, this._model.offsetY + p.y)
}
context.stroke();
}
}​
The above code produces the lines in the canvas. The line is one color, I want to at the beginning or in any municipal color was different. My code does not work: (why?. How to fix it?
Changing the color while you are constructing the path doesn't do anything. The color is applied only once, when stroke() is called, so the last strokeStyle you set will be the color of the entire line.
beginPath(), moveTo(), lineTo(), etc only create a path and that path itself has no color. Stroking or filling that path only ever apply a single color.
If you want a path that is multiple colors you will have to do one of two things:
Begin a path, do some number of lines, stroke it one color, and then begin another path that will be stroked with a different color. In other words:
// make a red line segment
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x, y);
ctx.strokeStyle = 'red';
ctx.stroke();
// Begin a new path and make this line segment green
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x, y);
ctx.strokeStyle = 'green';
ctx.stroke();
//etc
Or, depending on what you're doing you could also use a linearGradient

Canvas draws lines too thick

I want to draw lines on my canvas element at every 48px high. Here's my code (a little jquery selector included since I also use jQuery).
var $canvas = $('canvas')
, maxY = $canvas.outerHeight()
, maxX = $canvas.outerWidth()
, X = 0
, Y = 0
, ctx = $canvas.get(0).getContext('2d');
ctx.strokeStyle = "rgb(100,0,0)";
ctx.lineWidth = 1.0;
ctx.lineCap = "round";
while (Y < maxY) {
ctx.beginPath();
ctx.moveTo(X, Y);
ctx.lineTo(maxX, Y);
//ctx.closePath();
ctx.stroke();
Y += 48;
};
Y = 0;
What I experience is that my first line is crisp and 1px high. All my other lines are higher. Here's the result:
(source: ghentgators.be)
Change your initial Y to +0.5 (or -0.5) and you'll get nice lines.

How to dividing a semicircular region into colored segments using HTML Canvas

trying to divide a semicircular region into colored segments in HTML Canvas.
Here's what I tried,
ctx.save();
ctx.translate(c.width / 2, (c.height / 2)-1);
ctx.strokeStyle = "red"
ctx.lineWidth = 3;
ctx.lineCap = "round";
var x=400; // number of times lineTo strokes. Greater the value the better is the smoothness
var factor=1; //with =1, the entire semicirular region is filled.
for (var i = 0; i < x; i++) {
//ctx.rotate(Math.PI);
ctx.beginPath();
ctx.strokeStyle = "rgba(255,0,0,1)";
//ctx.rotate(-Math.PI/2);
ctx.rotate((-Math.PI * factor) / x);
//1st color segment, factor=1 helps to paint 100% of semicircular region
ctx.moveTo(122, 0);
ctx.lineTo(70, 0);
ctx.stroke();
//ctx.rotate(Math.PI); //2nd color segment
Alternate way, might be to use concentric arc() segments. I'm trying that now. But any one who can throw some light would be a great help.
the sample at http://www.phpied.com/wp-content/uploads/2008/02/canvas-pie.html was the one I was looking for.
Uses concentric arc() as I anticipated.

Canvas shapes becoming aliased when re-drawn in safari

I'm drawing a simple progress indicator using canvas. When the element is drawn for the first time it looks all nice and anti-aliased, but when drawn a second time, it loses it's anti-aliasing. Anyone know what could be going on here?
function drawProgress(id, percent) {
var selected = $(safeID(id)).is('.selected');
var canvas = $(safeID("CANVAS_" + id));
var ctx = $(canvas)[0].getContext('2d');
ctx.clearRect();
if ( selected ) {
ctx.fillStyle = "#ffffff";
ctx.strokeStyle = "#ffffff";
}
else {
ctx.fillStyle = "#99a7ca";
ctx.strokeStyle = "#99a7ca";
}
ctx.beginPath();
ctx.arc(canvas.width()/2.0, canvas.height()/2.0, canvas.width()/2.0-1, 0, Math.PI, false);
ctx.fill();
ctx.beginPath();
ctx.arc(canvas.width()/2.0, canvas.height()/2.0, canvas.width()/2.0-1, 0, Math.PI*2.0, false);
ctx.stroke();
}
You need to specify dimensions to clearRect.