Bezier Curve and Canvas

Getting S shape with Bezier curve on canvas in HTML

If the S shape is currently displayed as you wish, then the middle control points are such that their X-coordinate roughly matches the X-coordinate of the end point they are closest to, and the Y-coordinate is the same for both middle control points: it is about half-way the Y range of the end points.

So you can calculate the control points dynamically if you only have the two end points:

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");

function drawS(start, end) {
ctx.beginPath();
ctx.moveTo(...start);
let midY = (start[1] + end[1]) / 2;
ctx.bezierCurveTo(start[0], midY, end[0], midY, ...end);
ctx.stroke();
}

drawS([20, 20], [200, 180]);
<canvas id="myCanvas" width="300" height="200" style="border:1px solid #d3d3d3;"></canvas>

Canvas HTML - Smoothness between lineTo and Bezier curve

I found a good solution for my problem: Using line interpolation.

Having the following segments and imagine I want to smooth things between B and C, how can I choose some bezier points that would garantee a smooth curve instead of broken one?

The scenario before smoothing

First I interpolate the segment AB and find the point B', which is the point the line AB would touch the Y coordinate for C. Then I use the same process to find C':

Interpolating points

The points B' and C' make good points for the bezier in order to smooth things up to a horizontal line:

bezier curve using B' and C'

This is also being computationally simple enough since finding the a line equation is rather simple.

How to join 2 Bezier curves smoothly & continuously with HTML5 Canvas

Common tangent

To join two beziers smoothly you need to make the lines from the common point parallel thus defining the tangent at the end and start of the two beziers to be the same.

The following image illustrates this

Sample Image

The line that is defined by the two control points (C2, C1) and the common point (P) is the tangent of the curve at P. The length of the line segments have no constraints.

How?

There are dozens of ways to do this and how you do it is dependent on the requirements of the curve, the type of curve, and much more.

Example

I am not going to give a full example as it requires an understanding of vector maths and a cover all solution on the assumption you are not familiar with vector maths would be huge.

Thus the most basic pseudo code example uses the previous control and end points to calculate the next control point. ? represents unknowns which are not bound by constraints required to keep the lines parallel

 // From illustration in answer
corner = ? // Distance to next control point as fraction of distance
// from previous control point
C2 = {x:?, y:?} // Last control point of previous bezier
P = {x:?, y:?} // Start of next bezier
C1 = { // Next control point along line from previous and scaled
x: P.x + (P.x - C2.x) * corner,
y: P.y + (P.y - C2.y) * corner,
}

// two beziers with common point P
ctx.bezierCurveTo(?,?, C2.x, C2.y, P.x, P.y)
ctx.bezierCurveTo(C1.x, C1.y, ?, ?, ?, ?)

How to draw a fraction of a bezier curve with canvas?

You can use ctx.setLineDash([]) with ctx.lineDashOffset, that's the common way to simulate partial drawing of paths.

const ctx = document.querySelector('canvas').getContext('2d');

animate();

function animate(){
let i = 0;

drawCurve(i);

function drawCurve(start){
ctx.clearRect(0,0,300,150); // initial width and height of canvas

const line_len = 204; // to let the last part of curve stay

ctx.setLineDash([1000]); // bigger than curve length
ctx.lineDashOffset = -start; // changing parameter

ctx.beginPath();
ctx.moveTo(50, 20);
ctx.quadraticCurveTo(230, 30, 50, 100);
ctx.stroke();

const anim_id = requestAnimationFrame(() => drawCurve(++start));
if(start > line_len) cancelAnimationFrame(anim_id);
}
}
<canvas></canvas>

Canvas, make an image follow a Bezier curve

You can use the same function to get another point slightly before the current one on the curve, then use Math.atan2 to get the angle between the two points.

Then, you'll need to use ctx.translate() and ctx.rotate() to mutate the transformation matrix instead of setting the position in the .drawImage() call. (The .setTransform() call at the start of the animation method resets the matrix for each frame.)

I also added an "onion skin" effect here, so the motion is better seen.

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");

var statue = new Image();
statue.src = "https://i.ibb.co/3TvCH8n/liberty.png";

function getQuadraticBezierXYatPercent(startPt, controlPt, endPt, percent) {
var x =
Math.pow(1 - percent, 2) * startPt.x +
2 * (1 - percent) * percent * controlPt.x +
Math.pow(percent, 2) * endPt.x;
var y =
Math.pow(1 - percent, 2) * startPt.y +
2 * (1 - percent) * percent * controlPt.y +
Math.pow(percent, 2) * endPt.y;
return { x: x, y: y };
}

const startPt = { x: 600, y: 200 };
const controlPt = { x: 300, y: 100 };
const endPt = { x: 0, y: 200 };

var percent = 0;

statue.addEventListener("load", () => {
ctx.clearRect(0, 0, c.width, c.height);
animate();
});

function animate() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
// "Onion skin" effect so the last frame is slightly retained to better show the motion.
ctx.fillStyle = "rgba(255,255,255,0.1)";
ctx.fillRect(0, 0, c.width, c.height);

percent = (percent + 0.003) % 1;

var point = getQuadraticBezierXYatPercent(startPt, controlPt, endPt, percent);
var lastPoint = getQuadraticBezierXYatPercent(startPt, controlPt, endPt, percent - 0.003);
var angle = Math.atan2(lastPoint.y - point.y, lastPoint.x - point.x);

// Debug pointer line
ctx.beginPath();
ctx.moveTo(point.x, point.y);
ctx.lineTo(point.x + Math.cos(angle) * 50, point.y + Math.sin(angle) * 50);
ctx.stroke();

// Actual drawing
ctx.translate(point.x, point.y);
ctx.rotate(angle);
ctx.drawImage(statue, 0, 0, statue.width, statue.height, -50, -50, 100, 100);
requestAnimationFrame(animate);
}
<canvas id="myCanvas" width="600" height="400" style="border:1px solid #d3d3d3;">

Bezier curve and canvas

You can use Path.quadTo() or Path.cubicTo() for that. Examples can be found in the SDK Examples (FingerPaint). In your case you would simply need to calculate the middle point and pass then your three points to quadTo()..

Some code for you:

  • (x1,y1) and (x3,y3) are your starting and ending points respectively.
  • create the paint object only once (e.g. in your constructor)

    Paint paint = new Paint() {
    {
    setStyle(Paint.Style.STROKE);
    setStrokeCap(Paint.Cap.ROUND);
    setStrokeWidth(3.0f);
    setAntiAlias(true);
    }
    };

    final Path path = new Path();
    path.moveTo(x1, y1);

    final float x2 = (x3 + x1) / 2;
    final float y2 = (y3 + y1) / 2;
    path.quadTo(x2, y2, x3, y3);
    canvas.drawPath(path, paint);


Related Topics



Leave a reply



Submit