Displaying DIV inside a Canvas clipping path - html

I have a DIV container that I want to place inside a Canvas element, how do I set the clipping path of the #canvasContents to the Canvas shape?
<canvas id="myCanvas" width="500" height="400"></canvas>
<div id="canvasContents" width="500" height="400">canvas contents</div>
<script>
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var x = 120, y = 70;
context.beginPath();
context.moveTo(x, y);
context.bezierCurveTo(x, y, x + 6, y - 82, x + 98, y - 25);
context.bezierCurveTo(x + 210, y - 5, x + 325, y + 2, x + 283, y + 78);
context.bezierCurveTo(x + 244, y + 173, x + 237, y + 270, x + 138, y + 235);
context.bezierCurveTo(x - 29, y + 185, x - 145, y + 154, x - 65, y + 99);
context.closePath();
context.lineWidth = 1;
context.fillStyle = '#FFF';
context.fill();
context.strokeStyle = '#000';
context.stroke();
</script>

You can't place anything inside a canvas element. The content inside a canvas element will only show if canvas is not supported in a browser. In other cases it's ignored.
You can try to use SVG to place a div inside by wrapping the code inlined for the SVG but there are some restrictions related to external content.
You need to build an inline SVG like this:
var inlineSVG =
'<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50">' +
'<foreignObject width="100%" height="100%">' +
ourHTMLgoesHere +
'</foreignObject></svg>';
Then convert it to blob and url:
var svg = new Blob([inlineSVG], {type:"image/svg+xml;charset=utf-8"});
var url = URL.createObjectURL(svg);
Now you can set this as an image source, load it and then draw it to canvas.
For simplicity I made this function to do all the hard work (please see link above for details). Text can be any HTML including the div you need to wrap inside. Just remember it cannot contain links to external content (images, css, fonts etc.):
/**
* Canvas extension: drawHTMLText(txt, options)
* By Ken Fyrstenberg, Epistemex
* http://epistemex.com/
*
* USAGE:
* myContext.drawHTMLText(txt [, options]);
*
* var options = {x: startPosition,
* y: startPosition,
* width: maxWidth,
* height: maxHeight,
* callback: myFunction,
* callbackError: myErrorFunction}
*
* Each individual option is optional in themself. The callback
* on success contains an object with reference to result and
* originalText. Error callback is provided with the error object.
*
* License: MIT
*/
CanvasRenderingContext2D.prototype.drawHTMLText = function(txt, options) {
/// make sure we have an object if none was provided
options = options || {};
var ctx = this,
/// build inline SVG
iSVG =
'<svg xmlns="http://www.w3.org/2000/svg" width="' +
(options.width ? options.width : ctx.canvas.width) +
'" height="' +
(options.height ? options.height : ctx.canvas.height) +
'"><foreignObject width="100%" height="100%">' +
'<div xmlns="http://www.w3.org/1999/xhtml" style="font:' +
ctx.font + ';color:' + ctx.fillStyle + '">' +
txt +
"</div></foreignObject></svg>",
/// create Blob of inlined SVG
svg = new Blob([iSVG],{type:"image/svg+xml;charset=utf-8"}),
/// create URL (handle prefixed version)
domURL = self.URL || self.webkitURL || self,
url = domURL.createObjectURL(svg),
/// create Image
img = new Image;
/// handle image loading
img.onload = function () {
/// draw SVG to canvas
ctx.drawImage(img,
(options.x ? options.x : 0),
(options.y ? options.y : 0));
domURL.revokeObjectURL(url);
/// invoke callback if provided
if (typeof options.callback === 'function')
options.callback({result: img,
originalText: txt});
};
/// handle potential errors
img.onerror = function(e) {
if (typeof options.callbackError === 'function') {
options.callbackError(e);
} else {
console.log(e);
}
}
img.src = url;
}
If you need clipping just add that to the canvas itself using context.clip() after defining a path.

Live Demo
Here is one way to accomplish it, first I put the canvas element over the div just using absolute positioning like so
canvas {
position:absolute
}
#canvasContents {
position:absolute;
background:blue;
width:500px;
height:400px;
line-height:400px;
text-align:center
}
Then I fill the canvas and use clipping to show the effect.
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var x = 120,
y = 70;
context.fillRect(0, 0, canvas.width, canvas.height);
context.save();
context.beginPath();
context.moveTo(x, y);
context.bezierCurveTo(x, y, x + 6, y - 82, x + 98, y - 25);
context.bezierCurveTo(x + 210, y - 5, x + 325, y + 2, x + 283, y + 78);
context.bezierCurveTo(x + 244, y + 173, x + 237, y + 270, x + 138, y + 235);
context.bezierCurveTo(x - 29, y + 185, x - 145, y + 154, x - 65, y + 99);
context.closePath();
context.clip();
context.clearRect(0, 0, canvas.width, canvas.height);
ctx.restore();
The key with clipping is to save the context, draw your path, and then call clip. After that you can do a fill, or in this example a clearRect which will show whatever is positioned behind the canvas element.

Related

How to draw a rounded rectangle with one side open (no connecting line on left side) using HTML canvas?

I am trying to draw a rectangle (with three sides) which has rounded corners and no connecting line on the left hand side. For example, see below diagram (ignore imperfect hand drawing as the lengths are not drawn perfectly as parallel to each other):
This below example draws a full rectangle with rounded corners.
// Now you can just call
var ctx = document.getElementById("myCanvas").getContext("2d");
// Draw using default border radius,
// stroke it but no fill (function's default values)
roundRect(ctx, 5, 5, 50, 50);
// To change the color on the rectangle, just manipulate the context
ctx.strokeStyle = "rgb(255, 0, 0)";
ctx.fillStyle = "rgba(255, 255, 0, .5)";
roundRect(ctx, 100, 5, 100, 100, 20, true);
// Manipulate it again
ctx.strokeStyle = "#0f0";
ctx.fillStyle = "#ddd";
// Different radii for each corner, others default to 0
roundRect(ctx, 300, 5, 200, 100, {
tl: 50,
br: 25
}, true);
/**
* Draws a rounded rectangle using the current state of the canvas.
* If you omit the last three params, it will draw a rectangle
* outline with a 5 pixel border radius
* #param {CanvasRenderingContext2D} ctx
* #param {Number} x The top left x coordinate
* #param {Number} y The top left y coordinate
* #param {Number} width The width of the rectangle
* #param {Number} height The height of the rectangle
* #param {Number} [radius = 5] The corner radius; It can also be an object
* to specify different radii for corners
* #param {Number} [radius.tl = 0] Top left
* #param {Number} [radius.tr = 0] Top right
* #param {Number} [radius.br = 0] Bottom right
* #param {Number} [radius.bl = 0] Bottom left
* #param {Boolean} [fill = false] Whether to fill the rectangle.
* #param {Boolean} [stroke = true] Whether to stroke the rectangle.
*/
function roundRect(ctx, x, y, width, height, radius, fill, stroke) {
if (typeof stroke === 'undefined') {
stroke = true;
}
if (typeof radius === 'undefined') {
radius = 5;
}
if (typeof radius === 'number') {
radius = {tl: radius, tr: radius, br: radius, bl: radius};
} else {
var defaultRadius = {tl: 0, tr: 0, br: 0, bl: 0};
for (var side in defaultRadius) {
radius[side] = radius[side] || defaultRadius[side];
}
}
ctx.beginPath();
ctx.moveTo(x + radius.tl, y);
ctx.lineTo(x + width - radius.tr, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr);
ctx.lineTo(x + width, y + height - radius.br);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height);
ctx.lineTo(x + radius.bl, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl);
ctx.lineTo(x, y + radius.tl);
ctx.quadraticCurveTo(x, y, x + radius.tl, y);
ctx.closePath();
if (fill) {
ctx.fill();
}
if (stroke) {
ctx.stroke();
}
}
<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
So far I have tried below code by modifying the above one, but not able to draw it successfully. The
bottom part is getting messed up with fold in the bottom left corner and creating a diagonal which is not required at all.
// Now you can just call
var ctx = document.getElementById("myCanvas").getContext("2d");
// Draw using default border radius,
// stroke it but no fill (function's default values)
roundRect(ctx, 5, 5, 50, 50);
// To change the color on the rectangle, just manipulate the context
ctx.strokeStyle = "rgb(255, 0, 0)";
ctx.fillStyle = "rgba(255, 255, 0, .5)";
roundRect(ctx, 100, 5, 100, 100, 20, true);
// Manipulate it again
ctx.strokeStyle = "#0f0";
ctx.fillStyle = "#ddd";
// Different radii for each corner, others default to 0
roundRect(ctx, 300, 5, 200, 100, {
tl: 50,
br: 25
}, true);
/**
* Draws a rounded rectangle using the current state of the canvas.
* If you omit the last three params, it will draw a rectangle
* outline with a 5 pixel border radius
* #param {CanvasRenderingContext2D} ctx
* #param {Number} x The top left x coordinate
* #param {Number} y The top left y coordinate
* #param {Number} width The width of the rectangle
* #param {Number} height The height of the rectangle
* #param {Number} [radius = 5] The corner radius; It can also be an object
* to specify different radii for corners
* #param {Number} [radius.tl = 0] Top left
* #param {Number} [radius.tr = 0] Top right
* #param {Number} [radius.br = 0] Bottom right
* #param {Number} [radius.bl = 0] Bottom left
* #param {Boolean} [fill = false] Whether to fill the rectangle.
* #param {Boolean} [stroke = true] Whether to stroke the rectangle.
*/
function roundRect(ctx, x, y, width, height, radius, fill, stroke) {
if (typeof stroke === 'undefined') {
stroke = true;
}
if (typeof radius === 'undefined') {
radius = 5;
}
if (typeof radius === 'number') {
radius = {
tl: radius,
tr: radius,
br: radius,
bl: radius
};
} else {
var defaultRadius = {
tl: 0,
tr: 0,
br: 0,
bl: 0
};
for (var side in defaultRadius) {
radius[side] = radius[side] || defaultRadius[side];
}
}
ctx.beginPath();
ctx.moveTo(x + radius.tl, y);
ctx.lineTo(x + width - radius.tr, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr);
//ctx.lineTo(x + width, y + height - radius.br);
//ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height);
ctx.moveTo(x + radius.tl, y);
ctx.lineTo(x + radius.bl, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl);
ctx.lineTo(x + width - radius.bl, y + height + radius.tl);
ctx.quadraticCurveTo(x, y, x + radius.tl, y);
ctx.closePath();
if (fill) {
ctx.fill();
}
if (stroke) {
ctx.stroke();
}
}
<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
Please help.
Thanks
The below solution worked for me as per my requirement :
// Now you can just call
var ctx = document.getElementById("myCanvas").getContext("2d");
// Draw using default border radius,
// stroke it but no fill (function's default values)
roundRect(ctx, 5, 5, 50, 50);
// To change the color on the rectangle, just manipulate the context
ctx.strokeStyle = "rgb(255, 0, 0)";
ctx.fillStyle = "rgba(255, 255, 0, .5)";
roundRect(ctx, 100, 5, 100, 100, 20, true);
// Manipulate it again
ctx.strokeStyle = "#0f0";
ctx.fillStyle = "#ddd";
// Different radii for each corner, others default to 0
roundRect(ctx, 300, 5, 200, 100, {
tl: 50,
br: 25
}, true);
function roundRect(ctx, x, y, width, height, radius, fill, stroke) {
if (typeof stroke === 'undefined') {
stroke = true;
}
if (typeof radius === 'undefined') {
radius = 5;
}
if (typeof radius === 'number') {
radius = {tl: radius, tr: radius, br: radius, bl: radius};
} else {
var defaultRadius = {tl: 0, tr: 0, br: 0, bl: 0};
for (var side in defaultRadius) {
radius[side] = radius[side] || defaultRadius[side];
}
}
// ctx.lineTo(x + radius.bl, y + height);
//ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl);
ctx.beginPath();
ctx.moveTo(x +width , y);
ctx.lineTo(x + radius.bl , y);
ctx.quadraticCurveTo(x, y , x, y + radius.bl);
ctx.lineTo(x, y+ height -radius.bl);
ctx.quadraticCurveTo(x , y + height , x + radius.bl , y + height );
ctx.lineTo(x+width, y + height);
//ctx.stroke();
//ctx.closePath();
if (fill) {
ctx.fill();
}
if (stroke) {
ctx.stroke();
}
}
<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>

Animating drawing arcTo lines on canvas

I am trying to implement an animation of drawing an arcTo line on Canvas. For a straight line for example, the animation would be as follows
c = canvas.getContext("2d");
width = window.innerWidth;
height = window.innerHeight;
complete = false
var percent = 1
function drawEdge(x1, y1, x2, y2, color){
c.beginPath();
c.lineWidth = 10;
c.strokeStyle = color;
c.moveTo(x1, y1);
c.lineTo(x2, y2);
c.stroke();
c.closePath();
}
function getPosition(x1, y1, x2, y2, percentageBetweenPoints){
let xPosition = x1 + (x2 - x1) * (percentageBetweenPoints / 100);
let yPosition = y1 + (y2 - y1) * (percentageBetweenPoints / 100);
const position = {
x: xPosition,
y: yPosition,
}
return position
}
function drawLine(){
if (!complete){
requestAnimationFrame(drawLine);
}
if (percent >= 100){
complete = true;
percent = 100;
} else{
percent = percent + 1;
}
position = getPosition(300,300,1000,300,percent);
c.clearRect(0, 0 , width, height);
drawEdge(300,300,position.x,position.y, "black");
}
drawLine()
This creates an animation of a line being drawn across the screen. However, I am having trouble doing the same thing for arcTo lines. Is there any way to implement this?
You are looking for something like this?
let ctx = canvas.getContext('2d');
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.font = 'bold 18px Arial';
requestAnimationFrame(draw);
function draw(t) {
t = t % 5e3 / 5e3;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(canvas.width/2, canvas.height/2, 50, 0, t * 2 * Math.PI);
ctx.stroke();
ctx.fillText((t*100).toFixed(0), canvas.width/2, canvas.height/2);
requestAnimationFrame(draw);
}
<canvas id=canvas></canvas>
To Hack or not to Hack?
There are two ways to do this
Calculate the start, end, and length of each line segment, the start, end angle, direction (CW or CCW), and center of each arc segment. Basically repeating all the maths and logic (around 50 lines of code) that makes arcTo such a useful render function.
You can get details on how to approach the full solution from html5 canvas triangle with rounded corners
Use ctx.lineDash with a long dash and a long space. Move the dash over time with ctx.lineDashOffset giving the appearance of a line growing in length (see demo). The dash offset value is reversed, starting at max length and ending when zero.
NOTE there is one problem with this method. You don't know the length of the line, and thus you don`t know how long it will take for the line to be completed. You can make an estimation. To know the length of the line you must do all the calculations (well there abouts)
The Hack
As the second method is the easiest to implement and covers most needs I will demo that method.
Not much to say about it, it animates a path created by ctx.arcTo
Side benefit is it will animated any path rendered using ctx.stroke
requestAnimationFrame(mainLoop);
// Line is defined in unit space.
// Origin is at center of canvas, -1,-1 top left, 1, 1 bottom right
// Unit box is square and will be scaled to fit the canvas size.
// Note I did not use ctx.setTransform to better highlight what is scaled and what is not.
const ctx = canvas.getContext("2d");
var w, h, w2, h2; // canvas size and half size
var linePos; // current dash offset
var scale; // canvas scale
const LINE_WIDTH = 0.05; // in units
const LINE_STYLE = "#000"; // black
const LINE_SPEED = 1; // in units per second
const MAX_LINE_LENGTH = 9; // in units approx
const RADIUS = 0.08; //Arc radius in units
const SHAPE = [[0.4, 0.2], [0.8, 0.2], [0.5, 0.5], [0.95, 0.95], [0.0, 0.5], [-0.95, 0.95], [-0.5, 0.5], [-0.8, 0.2], [-0.2, 0.2], [-0.2, -0.2], [-0.8, -0.2], [-0.5, -0.5], [-0.95, -0.95], [0.0, -0.5], [0.95,-0.95], [0.5, -0.5], [0.8, -0.2], [0.2, -0.2], [0.2, 0.2], [0.6, 0.2], [0.8, 0.2]];
function sizeCanvas() {
w2 = (w = canvas.width = innerWidth) / 2;
h2 = (h = canvas.height = innerHeight) / 2;
scale = Math.min(w2, h2);
resetLine();
}
function addToPath(shape) {
var p1, p2;
for (p2 of shape) {
!p2.length ?
ctx.closePath() :
(p1 ? ctx.arcTo(p1[0] * scale + w2, p1[1] * scale + h2, p2[0] * scale + w2, p2[1] * scale + h2, RADIUS * scale) :
ctx.lineTo(p2[0] * scale + w2, p2[1] * scale + h2)
);
p1 = p2;
}
}
function resetLine() {
ctx.setLineDash([MAX_LINE_LENGTH * scale, MAX_LINE_LENGTH * scale]);
linePos = MAX_LINE_LENGTH * scale;
ctx.lineWidth = LINE_WIDTH * scale;
ctx.lineJoin = ctx.lineCap = "round";
}
function mainLoop() {
if (w !== innerWidth || h !== innerHeight) { sizeCanvas() }
else { ctx.clearRect(0, 0, w, h) }
ctx.beginPath();
addToPath(SHAPE);
ctx.lineDashOffset = (linePos -= LINE_SPEED * scale * (1 / 60));
ctx.stroke();
if (linePos <= 0) { resetLine() }
requestAnimationFrame(mainLoop);
}
body {
padding: 0px,
margin: 0px;
}
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id="canvas"></canvas>

No subpixel positioning on small HTML5 canvas on chrome

When animating an image over a large canvas, the image renders correctly on non-integer coordinates, and the animation is smooth.
on a small canvas, say 200x200, the subpixel coordinates don't apply, and the image "jumps" from integer location to the next, creating a "jittery" motion.
the issue seems to apply to raster sources only (images and canvases). text, for instance, animates smoothly on all canvas sizes.
i'm currently testing with Chrome Version 58.0.3029.110 (64-bit), however the issue appeared on earlier versions as well.
has anyone stumbled upon this issue?
here's the code i test with:
<!DOCTYPE HTML>
<html>
<head>
</head>
<body>
<script>
var outer = [200, 200];
var inner = [200, 200];
function CreateCanvas(w, h, hidden) {
var canvas = document.createElement('canvas');
if(!hidden) document.body.appendChild(canvas);
canvas.width = w;
canvas.height = h;
var context = canvas.getContext('2d');
return {canvas:canvas, context:context};
}
function rgba2hex(color) {
return "rgba(" + Math.floor(color[0] * 255) + ',' + Math.floor(color[1] * 255) + ',' + Math.floor(color[2] * 255) + ',' + color[3] + ")";
}
function GetSystemTimeMS() {
return (new Date()).getTime();
}
function GetTimeDifferenceMS(time) {
return GetSystemTimeMS() - time;
}
var outerFontSize = Math.min(100, outer[1] * 0.3);
var innerFontSize = Math.min(100, inner[1] * 0.3);
var outerBuffer = CreateCanvas(outer[0], outer[1], false);
outerBuffer.context.font = outerFontSize + "px times";
outerBuffer.context.fillStyle = rgba2hex([0,0,0,1]);
var innerBuffer = CreateCanvas(inner[0], inner[1], true);
innerBuffer.context.font = innerFontSize + "px times";
innerBuffer.context.fillStyle = rgba2hex([0,0,0,1]);
innerBuffer.context.fillText("raster", 10, inner[1] * 0.9);
var startTime = GetSystemTimeMS();
function draw() {
var span = 5;
var phase = ((GetTimeDifferenceMS(startTime) / 1000) % span) / span;
outerBuffer.context.clearRect(0, 0, outer[0], outer[1]);
var x = 50 + phase * 20;
outerBuffer.context.fillText("vector", x, outer[1] * 0.5);
outerBuffer.context.drawImage(innerBuffer.canvas, x, 0);
window.setTimeout(draw, 10);
}
draw();
</script>
</body>
</html>
I can definitely reproduce it on both my stable chrome and on my canary.
I reported to the chromium team. Let's hope a fix will come soon enough.
For a workaround, you can shrink a little bit your images (minimum value I found was size * 0.99. This should force the antialiasing algorithm to kick in.
var outer = [200, 200];
var inner = [200, 200];
function CreateCanvas(w, h, hidden) {
var canvas = document.createElement('canvas');
if (!hidden) document.body.appendChild(canvas);
canvas.width = w;
canvas.height = h;
var context = canvas.getContext('2d');
return {
canvas: canvas,
context: context
};
}
function rgba2hex(color) {
return "rgba(" + Math.floor(color[0] * 255) + ',' + Math.floor(color[1] * 255) + ',' + Math.floor(color[2] * 255) + ',' + color[3] + ")";
}
function GetSystemTimeMS() {
return (new Date()).getTime();
}
function GetTimeDifferenceMS(time) {
return GetSystemTimeMS() - time;
}
var outerFontSize = Math.min(100, outer[1] * 0.3);
var innerFontSize = Math.min(100, inner[1] * 0.3);
var outerBuffer = CreateCanvas(outer[0], outer[1], false);
outerBuffer.context.font = outerFontSize + "px times";
outerBuffer.context.fillStyle = rgba2hex([0, 0, 0, 1]);
var innerBuffer = CreateCanvas(inner[0], inner[1], true);
innerBuffer.context.font = innerFontSize + "px times";
innerBuffer.context.fillStyle = rgba2hex([0, 0, 0, 1]);
innerBuffer.context.fillText("raster", 10, inner[1] * 0.9);
var startTime = GetSystemTimeMS();
function draw() {
var span = 5;
var phase = ((GetTimeDifferenceMS(startTime) / 1000) % span) / span;
outerBuffer.context.clearRect(0, 0, outer[0], outer[1]);
var x = 50 + phase * 20;
outerBuffer.context.fillText("vector", x, outer[1] * 0.5);
// shrink a little bit our image
outerBuffer.context.drawImage(innerBuffer.canvas, x, 0, 200 * 0.99, 200 * 0.99);
requestAnimationFrame(draw);
}
draw();

Canvas arc and fill with gradient

I have the following source code:
HTML:
<canvas id="main" width="500" height="500" style="border:1px solid black;"></canvas>
JavaScript:
function myRnd(val)
{
return parseInt(Math.random() * val);
}
function rndCircles() {
var maxCircles = 30;
for (var r = 1; r <= maxCircles; r++) {
var c = document.getElementById("main");
var x = myRnd(c.clientWidth);
var y = myRnd(c.clientHeight);
var radius = parseInt(Math.random() * 30);
var ctx = c.getContext("2d");
// Create gradient
var grd = ctx.createRadialGradient(75, 50, 5, 90, 60, 100);
grd.addColorStop(0, 'rgb(' + myRnd(255) + ', ' + myRnd(255) + ',' + myRnd(255) + ')');
grd.addColorStop(1, 'white');
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI,false);
// Fill with gradient
ctx.fillStyle = grd;
ctx.fill();
ctx.lineWidth = 5;
//ctx.strokeStyle = '#003300';
ctx.stroke();
}
}
rndCircles();
I cannot see each arc filled with a different color/gradient on Chrome. Why? Am I missing something?
Fiddle here:
https://jsfiddle.net/j9wst2yd/
The gradient will use the initial position and radius when defined, which means the arcs further away from it will eventually turn completely white (or whatever the outer color would be).
You can solve this by using translation instead of setting position for the arcs.
Only a couple of adjustments are needed -
Create the gradient based around origin (0, 0):
var grd = ctx.createRadialGradient(0, 0, 5, 0, 0, 100);
// ...
Then replace setting arc position with translation, here absolute using setTransform() instead (translate() would accumulate, forcing you to reverse the translation afterwards which is more performance costly):
ctx.setTransform(1,0,0,1, x, y); // two last = translation
ctx.beginPath();
ctx.arc(0, 0, radius, 0, 2 * Math.PI,false); // draw at origin
To reset back to normal (identity matrix) just set (0,0) for setTransform():
ctx.setTransform(1, 0, 0, 1, 0, 0);
Adjust as needed.
function myRnd(val)
{
return parseInt(Math.random() * val);
}
function rndCircles() {
var maxCircles = 30;
var c = document.getElementById("main");
var ctx = c.getContext("2d");
for (var r = 1; r <= maxCircles; r++) {
var x = myRnd(c.clientWidth);
var y = myRnd(c.clientHeight);
var radius = parseInt(Math.random() * 30);
var grd = ctx.createRadialGradient(0, 0, 5, 0, 0, radius);
grd.addColorStop(0, 'rgb(' + myRnd(255) + ', ' + myRnd(255) + ',' + myRnd(255) + ')');
grd.addColorStop(1, 'white');
ctx.fillStyle = grd;
ctx.setTransform(1,0,0,1,x,y);
ctx.beginPath();
ctx.arc(0, 0, radius, 0, 2 * Math.PI,false);
// Fill with gradient
ctx.fill();
ctx.lineWidth = 5;
//ctx.strokeStyle = '#003300';
ctx.stroke();
}
}
rndCircles();
<canvas id="main" width="500" height="500" style="border:1px solid black;"></canvas>

How to draw a rounded rectangle using HTML Canvas?

HTML Canvas provides methods for drawing rectangles, fillRect() and strokeRect(), but I can't find a method for making rectangles with rounded corners. How can I do that?
Nowadays you can just use context.roundRect. See further details on Kaiido's answer
var ctx = document.getElementById("rounded-rect").getContext("2d");
ctx.beginPath();
// Draw using 5px for border radius on all sides
// stroke it but no fill
ctx.roundRect(5, 5, 50, 50, 5);
ctx.stroke();
// To change the color on the rectangle, just manipulate the context
ctx.strokeStyle = "rgb(255, 0, 0)";
ctx.fillStyle = "rgba(255, 255, 0, .5)";
ctx.beginPath();
ctx.roundRect(100, 5, 100, 100, 20);
ctx.stroke();
ctx.fill();
// Manipulate it again
ctx.strokeStyle = "#0f0";
ctx.fillStyle = "#ddd";
// Different radii for each corner, top-left clockwise to bottom-left
ctx.beginPath();
ctx.roundRect(300, 5, 200, 100, [50,0,25,0]);
ctx.fill();
ctx.stroke();
<canvas id="rounded-rect" width="500" height="200">
<!-- Insert fallback content here -->
</canvas>
Old answer:
I needed to do the same thing and created a method to do it.
/**
* Draws a rounded rectangle using the current state of the canvas.
* If you omit the last three params, it will draw a rectangle
* outline with a 5 pixel border radius
* #param {CanvasRenderingContext2D} ctx
* #param {Number} x The top left x coordinate
* #param {Number} y The top left y coordinate
* #param {Number} width The width of the rectangle
* #param {Number} height The height of the rectangle
* #param {Number} [radius = 5] The corner radius; It can also be an object
* to specify different radii for corners
* #param {Number} [radius.tl = 0] Top left
* #param {Number} [radius.tr = 0] Top right
* #param {Number} [radius.br = 0] Bottom right
* #param {Number} [radius.bl = 0] Bottom left
* #param {Boolean} [fill = false] Whether to fill the rectangle.
* #param {Boolean} [stroke = true] Whether to stroke the rectangle.
*/
function roundRect(
ctx,
x,
y,
width,
height,
radius = 5,
fill = false,
stroke = true
) {
if (typeof radius === 'number') {
radius = {tl: radius, tr: radius, br: radius, bl: radius};
} else {
radius = {...{tl: 0, tr: 0, br: 0, bl: 0}, ...radius};
}
ctx.beginPath();
ctx.moveTo(x + radius.tl, y);
ctx.lineTo(x + width - radius.tr, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr);
ctx.lineTo(x + width, y + height - radius.br);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height);
ctx.lineTo(x + radius.bl, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl);
ctx.lineTo(x, y + radius.tl);
ctx.quadraticCurveTo(x, y, x + radius.tl, y);
ctx.closePath();
if (fill) {
ctx.fill();
}
if (stroke) {
ctx.stroke();
}
}
// Now you can just call
var ctx = document.getElementById("rounded-rect").getContext("2d");
// Draw using default border radius,
// stroke it but no fill (function's default values)
roundRect(ctx, 5, 5, 50, 50);
// To change the color on the rectangle, just manipulate the context
ctx.strokeStyle = "rgb(255, 0, 0)";
ctx.fillStyle = "rgba(255, 255, 0, .5)";
roundRect(ctx, 100, 5, 100, 100, 20, true);
// Manipulate it again
ctx.strokeStyle = "#0f0";
ctx.fillStyle = "#ddd";
// Different radii for each corner, others default to 0
roundRect(ctx, 300, 5, 200, 100, {
tl: 50,
br: 25
}, true);
<canvas id="rounded-rect" width="500" height="200">
<!-- Insert fallback content here -->
</canvas>
Different radii per corner provided by Corgalore
See http://js-bits.blogspot.com/2010/07/canvas-rounded-corner-rectangles.html
for further explanation
I started with #jhoff's solution, but rewrote it to use width/height parameters, and using arcTo makes it quite a bit more terse:
CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) {
if (w < 2 * r) r = w / 2;
if (h < 2 * r) r = h / 2;
this.beginPath();
this.moveTo(x+r, y);
this.arcTo(x+w, y, x+w, y+h, r);
this.arcTo(x+w, y+h, x, y+h, r);
this.arcTo(x, y+h, x, y, r);
this.arcTo(x, y, x+w, y, r);
this.closePath();
return this;
}
Also returning the context so you can chain a little. E.g.:
ctx.roundRect(35, 10, 225, 110, 20).stroke(); //or .fill() for a filled rect
The HTML5 canvas doesn't provide a method to draw a rectangle with rounded corners.
How about using the lineTo() and arc() methods?
You can also use the quadraticCurveTo() method instead of the arc() method.
Juan, I made a slight improvement to your method to allow for changing each rectangle corner radius individually:
/**
* Draws a rounded rectangle using the current state of the canvas.
* If you omit the last three params, it will draw a rectangle
* outline with a 5 pixel border radius
* #param {Number} x The top left x coordinate
* #param {Number} y The top left y coordinate
* #param {Number} width The width of the rectangle
* #param {Number} height The height of the rectangle
* #param {Object} radius All corner radii. Defaults to 0,0,0,0;
* #param {Boolean} fill Whether to fill the rectangle. Defaults to false.
* #param {Boolean} stroke Whether to stroke the rectangle. Defaults to true.
*/
CanvasRenderingContext2D.prototype.roundRect = function (x, y, width, height, radius, fill, stroke) {
var cornerRadius = { upperLeft: 0, upperRight: 0, lowerLeft: 0, lowerRight: 0 };
if (typeof stroke == "undefined") {
stroke = true;
}
if (typeof radius === "object") {
for (var side in radius) {
cornerRadius[side] = radius[side];
}
}
this.beginPath();
this.moveTo(x + cornerRadius.upperLeft, y);
this.lineTo(x + width - cornerRadius.upperRight, y);
this.quadraticCurveTo(x + width, y, x + width, y + cornerRadius.upperRight);
this.lineTo(x + width, y + height - cornerRadius.lowerRight);
this.quadraticCurveTo(x + width, y + height, x + width - cornerRadius.lowerRight, y + height);
this.lineTo(x + cornerRadius.lowerLeft, y + height);
this.quadraticCurveTo(x, y + height, x, y + height - cornerRadius.lowerLeft);
this.lineTo(x, y + cornerRadius.upperLeft);
this.quadraticCurveTo(x, y, x + cornerRadius.upperLeft, y);
this.closePath();
if (stroke) {
this.stroke();
}
if (fill) {
this.fill();
}
}
Use it like this:
var canvas = document.getElementById("canvas");
var c = canvas.getContext("2d");
c.fillStyle = "blue";
c.roundRect(50, 100, 50, 100, {upperLeft:10,upperRight:10}, true, true);
Good news everyone!
roundRect(x, y, width, height, radii); is now officially part of the Canvas 2D API.
It is exposed on CanvasRenderingContext2D, Path2D and OffscreenCanvasRenderingContext2D objects.
Its radii parameter is an Array which contains either
a single float, representing the radius to use for all four corners,
two floats, for the top-left + bottom-right and top-right + bottom-left corners respectively,
three floats, for the top-left, top-right + bottom-left and bottom-right respectively,
or four floats, one per corner,
OR the same combinations, but with a DOMPointInit object, representing the x-radius and y-radius of each corner.
Currently, only Chrome has an implementation available, but you can find a polyfill I made, in this repo.
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.roundRect(20,20,80,80,[new DOMPoint(60,80), new DOMPoint(110,100)]);
ctx.strokeStyle = "green";
ctx.stroke();
const path = new Path2D();
path.roundRect(120,30,60,90,[0,25,new DOMPoint(60,80), new DOMPoint(110,100)]);
ctx.fillStyle = "purple";
ctx.fill(path);
// and a simple one
ctx.beginPath();
ctx.roundRect(200,20,80,80,[10]);
ctx.fillStyle = "orange";
ctx.fill();
<script src="https://cdn.jsdelivr.net/gh/Kaiido/roundRect#main/roundRect.js"></script>
<canvas></canvas>
This code creates a 100-pixel square, with rounded corners of 30 pixels.
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(100,100);
ctx.arcTo(0,100,0,0,30);
ctx.arcTo(0,0,100,0,30);
ctx.arcTo(100,0,100,100,30);
ctx.arcTo(100,100,0,100,30);
ctx.fill();
The drawPolygon function below can be used to draw any polygon with rounded corners.
See it running here.
function drawPolygon(ctx, pts, radius) {
if (radius > 0) {
pts = getRoundedPoints(pts, radius);
}
var i, pt, len = pts.length;
ctx.beginPath();
for (i = 0; i < len; i++) {
pt = pts[i];
if (i == 0) {
ctx.moveTo(pt[0], pt[1]);
} else {
ctx.lineTo(pt[0], pt[1]);
}
if (radius > 0) {
ctx.quadraticCurveTo(pt[2], pt[3], pt[4], pt[5]);
}
}
ctx.closePath();
}
function getRoundedPoints(pts, radius) {
var i1, i2, i3, p1, p2, p3, prevPt, nextPt,
len = pts.length,
res = new Array(len);
for (i2 = 0; i2 < len; i2++) {
i1 = i2-1;
i3 = i2+1;
if (i1 < 0) {
i1 = len - 1;
}
if (i3 == len) {
i3 = 0;
}
p1 = pts[i1];
p2 = pts[i2];
p3 = pts[i3];
prevPt = getRoundedPoint(p1[0], p1[1], p2[0], p2[1], radius, false);
nextPt = getRoundedPoint(p2[0], p2[1], p3[0], p3[1], radius, true);
res[i2] = [prevPt[0], prevPt[1], p2[0], p2[1], nextPt[0], nextPt[1]];
}
return res;
};
function getRoundedPoint(x1, y1, x2, y2, radius, first) {
var total = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)),
idx = first ? radius / total : (total - radius) / total;
return [x1 + (idx * (x2 - x1)), y1 + (idx * (y2 - y1))];
};
The function receives an array with the polygon points, like this:
var canvas = document.getElementById("cv");
var ctx = canvas.getContext("2d");
ctx.strokeStyle = "#000000";
ctx.lineWidth = 5;
drawPolygon(ctx, [[20, 20],
[120, 20],
[120, 120],
[ 20, 120]], 10);
ctx.stroke();
This is a port and a more generic version of a solution posted here.
Here's one I wrote... uses arcs instead of quadratic curves for better control over radius. Also, it leaves the stroking and filling up to you
/* Canvas 2d context - roundRect
*
* Accepts 5 parameters:
the start_x,
start_y points,
the end_x,
end_y points,
the radius of the corners
*
* No return value
*/
CanvasRenderingContext2D.prototype.roundRect = function(sx,sy,ex,ey,r) {
var r2d = Math.PI/180;
if( ( ex - sx ) - ( 2 * r ) < 0 ) { r = ( ( ex - sx ) / 2 ); } //ensure that the radius isn't too large for x
if( ( ey - sy ) - ( 2 * r ) < 0 ) { r = ( ( ey - sy ) / 2 ); } //ensure that the radius isn't too large for y
this.beginPath();
this.moveTo(sx+r,sy);
this.lineTo(ex-r,sy);
this.arc(ex-r,sy+r,r,r2d*270,r2d*360,false);
this.lineTo(ex,ey-r);
this.arc(ex-r,ey-r,r,r2d*0,r2d*90,false);
this.lineTo(sx+r,ey);
this.arc(sx+r,ey-r,r,r2d*90,r2d*180,false);
this.lineTo(sx,sy+r);
this.arc(sx+r,sy+r,r,r2d*180,r2d*270,false);
this.closePath();
}
Here is an example:
var _e = document.getElementById('#my_canvas');
var _cxt = _e.getContext("2d");
_cxt.roundRect(35,10,260,120,20);
_cxt.strokeStyle = "#000";
_cxt.stroke();
So this is based out of using lineJoin="round" and with the proper proportions, mathematics and logic I have been able to make this function, this is not perfect but hope it helps. If you want to make each corner have a different radius take a look at: https://p5js.org/reference/#/p5/rect
Here ya go:
CanvasRenderingContext2D.prototype.roundRect = function (x,y,width,height,radius) {
radius = Math.min(Math.max(width-1,1),Math.max(height-1,1),radius);
var rectX = x;
var rectY = y;
var rectWidth = width;
var rectHeight = height;
var cornerRadius = radius;
this.lineJoin = "round";
this.lineWidth = cornerRadius;
this.strokeRect(rectX+(cornerRadius/2), rectY+(cornerRadius/2), rectWidth-cornerRadius, rectHeight-cornerRadius);
this.fillRect(rectX+(cornerRadius/2), rectY+(cornerRadius/2), rectWidth-cornerRadius, rectHeight-cornerRadius);
this.stroke();
this.fill();
}
CanvasRenderingContext2D.prototype.roundRect = function (x,y,width,height,radius) {
radius = Math.min(Math.max(width-1,1),Math.max(height-1,1),radius);
var rectX = x;
var rectY = y;
var rectWidth = width;
var rectHeight = height;
var cornerRadius = radius;
this.lineJoin = "round";
this.lineWidth = cornerRadius;
this.strokeRect(rectX+(cornerRadius/2), rectY+(cornerRadius/2), rectWidth-cornerRadius, rectHeight-cornerRadius);
this.fillRect(rectX+(cornerRadius/2), rectY+(cornerRadius/2), rectWidth-cornerRadius, rectHeight-cornerRadius);
this.stroke();
this.fill();
}
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext('2d');
function yop() {
ctx.clearRect(0,0,1000,1000)
ctx.fillStyle = "#ff0000";
ctx.strokeStyle = "#ff0000"; ctx.roundRect(Number(document.getElementById("myRange1").value),Number(document.getElementById("myRange2").value),Number(document.getElementById("myRange3").value),Number(document.getElementById("myRange4").value),Number(document.getElementById("myRange5").value));
requestAnimationFrame(yop);
}
requestAnimationFrame(yop);
<input type="range" min="0" max="1000" value="10" class="slider" id="myRange1"><input type="range" min="0" max="1000" value="10" class="slider" id="myRange2"><input type="range" min="0" max="1000" value="200" class="slider" id="myRange3"><input type="range" min="0" max="1000" value="100" class="slider" id="myRange4"><input type="range" min="1" max="1000" value="50" class="slider" id="myRange5">
<canvas id="myCanvas" width="1000" height="1000">
</canvas>
Here's a solution using the lineJoin property to round the corners. It works if you just need a solid shape, but not so much if you need a thin border that's smaller than the border radius.
function roundedRect(ctx, options) {
ctx.strokeStyle = options.color;
ctx.fillStyle = options.color;
ctx.lineJoin = "round";
ctx.lineWidth = options.radius;
ctx.strokeRect(
options.x+(options.radius*.5),
options.y+(options.radius*.5),
options.width-options.radius,
options.height-options.radius
);
ctx.fillRect(
options.x+(options.radius*.5),
options.y+(options.radius*.5),
options.width-options.radius,
options.height-options.radius
);
ctx.stroke();
ctx.fill();
}
const canvas = document.getElementsByTagName("canvas")[0];
const ctx = canvas.getContext("2d");
roundedRect(ctx, {
x: 10,
y: 10,
width: 200,
height: 100,
radius: 35,
color: "red"
});
<canvas></canvas>
Opera, ffs.
if (window["CanvasRenderingContext2D"]) {
/** #expose */
CanvasRenderingContext2D.prototype.roundRect = function(x, y, w, h, r) {
if (w < 2*r) r = w/2;
if (h < 2*r) r = h/2;
this.beginPath();
if (r < 1) {
this.rect(x, y, w, h);
} else {
if (window["opera"]) {
this.moveTo(x+r, y);
this.arcTo(x+r, y, x, y+r, r);
this.lineTo(x, y+h-r);
this.arcTo(x, y+h-r, x+r, y+h, r);
this.lineTo(x+w-r, y+h);
this.arcTo(x+w-r, y+h, x+w, y+h-r, r);
this.lineTo(x+w, y+r);
this.arcTo(x+w, y+r, x+w-r, y, r);
} else {
this.moveTo(x+r, y);
this.arcTo(x+w, y, x+w, y+h, r);
this.arcTo(x+w, y+h, x, y+h, r);
this.arcTo(x, y+h, x, y, r);
this.arcTo(x, y, x+w, y, r);
}
}
this.closePath();
};
/** #expose */
CanvasRenderingContext2D.prototype.fillRoundRect = function(x, y, w, h, r) {
this.roundRect(x, y, w, h, r);
this.fill();
};
/** #expose */
CanvasRenderingContext2D.prototype.strokeRoundRect = function(x, y, w, h, r) {
this.roundRect(x, y, w, h, r);
this.stroke();
};
}
Since Opera is going WebKit, this should also remain valid in the legacy case.
To make the function more consistent with the normal means of using a canvas context, the canvas context class can be extended to include a 'fillRoundedRect' method -- that can be called in the same way fillRect is called:
var canv = document.createElement("canvas");
var cctx = canv.getContext("2d");
// If thie canvasContext class doesn't have a fillRoundedRect, extend it now
if (!cctx.constructor.prototype.fillRoundedRect) {
// Extend the canvaseContext class with a fillRoundedRect method
cctx.constructor.prototype.fillRoundedRect =
function (xx,yy, ww,hh, rad, fill, stroke) {
if (typeof(rad) == "undefined") rad = 5;
this.beginPath();
this.moveTo(xx+rad, yy);
this.arcTo(xx+ww, yy, xx+ww, yy+hh, rad);
this.arcTo(xx+ww, yy+hh, xx, yy+hh, rad);
this.arcTo(xx, yy+hh, xx, yy, rad);
this.arcTo(xx, yy, xx+ww, yy, rad);
if (stroke) this.stroke(); // Default to no stroke
if (fill || typeof(fill)=="undefined") this.fill(); // Default to fill
}; // end of fillRoundedRect method
}
The code checks to see if the prototype for the constructor for the canvas context object contains a 'fillRoundedRect' property and adds one -- the first time around. It is invoked in the same manner as the fillRect method:
ctx.fillStyle = "#eef"; ctx.strokeStyle = "#ddf";
// ctx.fillRect(10,10, 200,100);
ctx.fillRoundedRect(10,10, 200,100, 5);
The method uses the arcTo method as Grumdring did. In the method, this is a reference to the ctx object. The stroke argument defaults to false if undefined. The fill argument defaults to fill the rectangle if undefined.
(Tested on Firefox, I don't know if all implementations permit extension in this manner.)
Method 1: Using path-drawing methods
The most straightforward method of doing this with HTML Canvas is by using the path-drawing methods of ctx:
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
function roundedRect(ctx, x, y, width, height, radius) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
}
ctx.fillStyle = "red";
roundedRect(ctx, 10, 10, 100, 100, 20);
ctx.fill();
<canvas id="canvas">
<!-- Fallback content -->
</canvas>
Method 2: Using Path2D
You can also draw rounded rectangles in HTML Canvas by using the Path2D interface:
Example 1
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
function roundedRect(x, y, width, height, radius) {
return new Path2D(`M ${x + radius} ${y} H ${x + width - radius} a ${radius} ${radius} 0 0 1 ${radius} ${radius} V ${y + height - radius} a ${radius} ${radius} 0 0 1 ${-radius} ${radius} H ${x + radius} a ${radius} ${radius} 0 0 1 ${-radius} ${-radius} V ${y + radius} a ${radius} ${radius} 0 0 1 ${radius} ${-radius}`);
}
ctx.fillStyle = "blue";
ctx.fill(roundedRect(10, 10, 100, 100, 20));
<canvas id="canvas">
<!-- Fallback content -->
</canvas>
Example 2
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
function roundedRect(x, y, width, height, radius) {
let path = new Path2D();
path.moveTo(x + radius, y);
path.lineTo(x + width - radius, y);
path.quadraticCurveTo(x + width, y, x + width, y + radius);
path.lineTo(x + width, y + height - radius);
path.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
path.lineTo(x + radius, y + height);
path.quadraticCurveTo(x, y + height, x, y + height - radius);
path.lineTo(x, y + radius);
path.quadraticCurveTo(x, y, x + radius, y);
path.closePath();
return path;
}
ctx.fillStyle = "green";
ctx.fill(roundedRect(10, 10, 100, 100, 20));
<canvas id="canvas">
<!-- Fallback content -->
</canvas>
try to add this line , when you want to get rounded corners : ctx.lineCap = "round";
NONE of the other answers can handle the following 3 cases correctly:
if ((width >= radius x 2) && (height <= radius * 2))
if ((width <= radius x 2) && (height >= radius * 2))
if ((width <= radius x 2) && (height <= radius * 2))
If any of these cases happen, you will not get a correctly drawn rectangle
My Solution handles ANY radius and ANY Width and Height dynamically, and should be the default answer
function roundRect(ctx, x, y, width, height, radius) {
/*
* Draws a rounded rectangle using the current state of the canvas.
*/
let w = width;
let h = height;
let r = radius;
ctx.stroke()
ctx.fill()
ctx.beginPath();
// Configure the roundedness of the rectangles corners
if ((w >= r * 2) && (h >= r * 2)) {
// Handles width and height larger than diameter
// Keep radius fixed
ctx.moveTo(x + r, y); // tr start
ctx.lineTo(x + w - r, y); // tr
ctx.quadraticCurveTo(x + w, y, x + w, y + r); //tr
ctx.lineTo(x + w, y + h - r); // br
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); // br
ctx.lineTo(x + r, y + h); // bl
ctx.quadraticCurveTo(x, y + h, x, y + h - r); // bl
ctx.lineTo(x, y + r); // tl
ctx.quadraticCurveTo(x, y, x + r, y); // tl
} else if ((w < r * 2) && (h > r * 2)) {
// Handles width lower than diameter
// Radius must dynamically change as half of width
r = w / 2;
ctx.moveTo(x + w, y + h - r); // br start
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); // br curve
ctx.quadraticCurveTo(x, y + h, x, y + h - r) // bl curve
ctx.lineTo(x, y + r); // line
ctx.quadraticCurveTo(x, y, x + r, y); // tl
ctx.quadraticCurveTo(x + w, y, x + w, y + r); // tl
ctx.lineTo(x + w, y + h - r); // line
} else if ((w > r * 2) && (h < r * 2)) {
// Handles height lower than diameter
// Radius must dynamically change as half of height
r = h / 2;
ctx.moveTo(x + w - r, y + h); // br start
ctx.quadraticCurveTo(x + w, y + h, x + w, y + r); // br curve
ctx.quadraticCurveTo(x + w, y, x + w - r, y); // tr curve
ctx.lineTo(x + r, y); // line between tr tl
ctx.quadraticCurveTo(x, y, x, y + r); // tl curve
ctx.quadraticCurveTo(x, y + h, x + r, y + h); // bl curve
} else if ((w < 2 * r) && (h < 2 * r)) {
// Handles width and height lower than diameter
ctx.moveTo(x + w / 2, y + h);
ctx.quadraticCurveTo(x + w, y + h, x + w, y + h / 2); // bl curve
ctx.quadraticCurveTo(x + w, y, x + w / 2, y); // tr curve
ctx.quadraticCurveTo(x, y, x, y + h / 2); // tl curve
ctx.quadraticCurveTo(x, y + h, x + w / 2, y + h); // bl curve
}
ctx.closePath();
}