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?
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'
:
The points B'
and C'
make good points for the bezier in order to smooth things up to a horizontal line:
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
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
Android:Change Button Text and Background Color
Sqlite Query in Android to Count Rows
Multiple Apps Use Same Content Provider
Failed to Launch Emulator: Error: Emulator Didn't Connect Within 60 Seconds
How to Open Navigation Drawer with No Actionbar, Open with Just a Button
Android 5.0: How to Change Recent Apps Title Color
Proguard Warnings "Can't Write Resource [Meta-Inf/Manifest.Mf] (Duplicate Zip Entry)"
Tablayout Tab Title Text in Lower Case
How to Use Getlistview() in Activity
Updated Sdk Version, Getting Classnotfoundexception: Android.Support.V4.View.Viewpager
How Can Retrofit 2.0 Parse Nested JSON Object
Play Downloaded Gif Image in Android
Clear Application Cache on Exit in Android
How to Fix "Fail to Connect to Camera Service" Exception in Android Emulator
How to Set Text of Text View in Another Thread
Android Studio 3.0 Compile Issue (Cannot Choose Between Configurations)