How to draw rounded image with text in HTML Canvas? - html

I am trying to draw image with rounded shape and the text on the bottom. But when I use ctx.clip() to round image corners, text dissappears.
Text with rectangle image works fine but I need images cornes to be rounded.
ctx.save();
ctx.beginPath();
ctx.fillStyle = `rgb(100, 100, 100, 0.8)`;
ctx.arc(x, y, z / 2, 0, 2 * Math.PI, false);
ctx.clip();
ctx.drawImage(img.img, x - 10, y - 10, 20, 20);
ctx.fill();
ctx.font = `${3.5}px Arial`;
ctx.fillStyle = "black";
ctx.textAlign = "center";
ctx.fillText('TEXT', x, y + 13.5);

Without a workable code it’s hard to t/s. I think you just need to restore after ctx.fill() though.
ctx.save();
ctx.beginPath();
ctx.fillStyle = `rgb(100, 100, 100, 0.8)`;
ctx.arc(x, y, z / 2, 0, 2 * Math.PI, false);
ctx.clip();
ctx.drawImage(img.img, x - 10, y - 10, 20, 20);
ctx.fill();
ctx.restore(); //add this
ctx.font = `${3.5}px Arial`;
ctx.fillStyle = "black";
ctx.textAlign = "center";
ctx.fillText('TEXT', x, y + 13.5);

Related

HTML canvas : how to create a polygon filled with a grid

I order to build a HTML 5 datacenter floor plan, I would like to create a polygon filled with a grid. This grid must not be a picture pattern as I would like to be able to zoom or rotate the floor plan without having pixelization.
I would like to be able to create this kind of output :
How can I do that ?
There are multiple ways, like
using a clipping region
var ctx = c.getContext('2d');
drawShape();
ctx.stroke();
ctx.save(); // so we can remove the clipping
ctx.clip();
drawGrid();
ctx.restore(); // remove the clipping
function drawShape() {
ctx.beginPath();
var pts = [
20, 20,
80, 20,
90, 50,
120, 90,
30, 80,
20,20
];
for(var i=0;i<pts.length;i+=2){
ctx.lineTo(pts[i], pts[i+1]);
}
}
function drawGrid() {
ctx.beginPath();
for(var x=-.5; x<c.width; x+=20) {
ctx.moveTo(x, 0);
ctx.lineTo(x, c.height);
}
for(var y=-.5; y<c.height; y+=20) {
ctx.moveTo(0, y);
ctx.lineTo(c.width, y);
}
ctx.stroke();
}
<canvas id="c"></canvas>
using compositing
var ctx = c.getContext('2d');
drawGrid();
ctx.globalCompositeOperation = 'destination-in';
drawShape();
ctx.fill();
ctx.globalCompositeOperation = 'source-over';
ctx.stroke();
function drawShape() {
ctx.beginPath();
var pts = [
20, 20,
80, 20,
90, 50,
120, 90,
30, 80,
20,20
];
for(var i=0;i<pts.length;i+=2){
ctx.lineTo(pts[i], pts[i+1]);
}
}
function drawGrid() {
ctx.beginPath();
for(var x=-.5; x<c.width; x+=20) {
ctx.moveTo(x, 0);
ctx.lineTo(x, c.height);
}
for(var y=-.5; y<c.height; y+=20) {
ctx.moveTo(0, y);
ctx.lineTo(c.width, y);
}
ctx.stroke();
}
<canvas id="c"></canvas>
But in your case, a regular grid, it might actually be better to use a pattern.
Indeed, you'd have to only draw one cell every time you change the scale of your grid, for translations, this can be done internally.
So I didn't do the performance tests myself, and thus encourage you to double check it's worth it, but theoretically, it might be faster and esaier to manage than redrawing the grid every time.
var ctx = c.getContext('2d');
var pat_ctx = document.createElement('canvas').getContext('2d');
var cell_size = 20;
// just a basic drawing example
// first we generate the grid as a pattern
ctx.fillStyle = generatePattern(cell_size, cell_size);
drawShape();
ctx.stroke();
// we move the pattern by half a cell because we actually drawn only a cross
ctx.translate(-cell_size / 2, -cell_size / 2);
ctx.fill();
// make the grid follow the mouse
// without having to redraw ourself the grid
onmousemove = function(e) {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, c.width, c.height);
drawShape();
ctx.stroke();
// move the grid
ctx.translate(e.clientX - cell_size / 2, e.clientY - -cell_size / 2);
ctx.fill();
}
// click to zoom (+shift to zoom out)
onclick = function(e) {
if (e.shiftKey) cell_size--;
else cell_size++;
ctx.fillStyle = generatePattern(cell_size, cell_size);
onmousemove(e);
}
// dimply draws a cross
function generatePattern(w, h) {
var canvas = pat_ctx.canvas;
canvas.width = w;
canvas.height = h;
pat_ctx.moveTo(w / 2, 0);
pat_ctx.lineTo(w / 2, h);
pat_ctx.moveTo(0, h / 2);
pat_ctx.lineTo(w, h / 2);
pat_ctx.stroke();
return pat_ctx.createPattern(canvas, 'repeat');
}
function drawShape() {
ctx.beginPath();
var pts = [
20, 20,
80, 20,
90, 50,
120, 90,
30, 80,
20, 20
];
for (var i = 0; i < pts.length; i += 2) {
ctx.lineTo(pts[i], pts[i + 1]);
}
}
<canvas id="c"></canvas>

Pulse animation in canvas

I am trying to make various shapes have a pulse like effect in canvas and managed to do it with a circle,
function drawCircle() {
// color in the background
context.fillStyle = "#EEEEEE";
context.fillRect(0, 0, canvas.width, canvas.height);
// draw the circle
context.beginPath();
var radius = 25 + 20 * Math.abs(Math.cos(angle)); //radius of circle
context.arc(25, 25, radius, 0, Math.PI * 2, false); //position on canvas
context.closePath();
// color in the circle
context.fillStyle = "#006699";
context.fill();
//'pulse'
angle += Math.PI / 220;
requestAnimationFrame(drawCircle);
}
drawCircle();
but I'm not sure how to go about doing any other shape. What I have so far for my triangle is
function drawTriangle() {
// draw the triangle
context.beginPath();
context.moveTo(75, 50);
context.lineTo(100, 75);
context.lineTo(100, 25);
context.fill();
context.rect(215, 100, Math.PI * 2, false); //position on canvas
context.closePath();
// color in the triangle
context.fillStyle = "#3f007f";
context.fill();
//'pulse'
angle += Math.PI / 280;
requestAnimationFrame(drawTriangle);
}
drawTriangle();
Any insight would be appreciated.
This can be simply achieved by changing the scale of the context matrix.
All you need to find is the position of the scaling anchor of your shape so that you can translate the matrix to the correct position after the scale has been applied.
In following example, I'll use the center of the shape as scaling anchor, since it seems it is what you wanted.
The extended version of the matrix transformations would be
ctx.translate(anchorX, anchorY);
ctx.scale(scaleFactor, scaleFactor);
ctx.translate(-anchorX, -anchorY);
which in below example has been reduced to
ctx.setTransform(
scale, 0, 0,
scale, anchorX - (anchorX * scale), anchorY - (anchorY * scale)
);
var ctx = canvas.getContext('2d');
var angle = 0;
var scale = 1;
var img = new Image();
img.src = 'https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png';
anim();
function anim() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
updateScale();
drawCircle();
drawTriangle();
drawImage();
ctx.setTransform(1, 0, 0, 1, 0, 0);
requestAnimationFrame(anim);
}
function updateScale() {
angle += Math.PI / 220;
scale = 0.5 + Math.abs(Math.cos(angle));
}
function drawCircle() {
ctx.beginPath();
var cx = 75,
cy = 50,
radius = 25;
// for the circle, centerX and centerY are given
var anchorX = cx,
anchorY = cy;
// with these anchorX, anchorY and scale,
// we can determine where we need to translate our context once scaled
var scaledX = anchorX - (anchorX * scale),
scaledY = anchorY - (anchorY * scale);
// then we apply the matrix in one go
ctx.setTransform(scale, 0, 0, scale, scaledX, scaledY);
// and we draw normally
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
ctx.fill();
}
function drawTriangle() {
ctx.beginPath();
// for the triangle, we need to find the position between minX and maxX,
// and between minY and maxY
var anchorX = 175 + (200 - 175) / 2,
anchorY = 25 + (75 - 25) / 2;
var scaledX = anchorX - (anchorX * scale),
scaledY = anchorY - (anchorY * scale);
ctx.setTransform(scale, 0, 0, scale, scaledX, scaledY);
ctx.moveTo(175, 50);
ctx.lineTo(200, 75);
ctx.lineTo(200, 25);
ctx.fill();
}
function drawImage() {
if (!img.naturalWidth) return;
// for rects, it's just pos + (length / 2)
var anchorX = 250 + img.naturalWidth / 2,
anchorY = 25 + img.naturalHeight / 2;
var scaledX = anchorX - (anchorX * scale),
scaledY = anchorY - (anchorY * scale);
ctx.setTransform(scale, 0, 0, scale, scaledX, scaledY);
ctx.drawImage(img, 250, 25);
}
<canvas id="canvas" width="500"></canvas>

Drawing non-gradient circle with colorstop

How can I draw a non-gradient circle with colorstop, something like this:
The closest I got was using radial gradient http://jsfiddle.net/8tdz0bo4/2/:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var x = 100,
y = 75,
innerRadius = 1,
outerRadius = 50,
radius = 60;
var gradient = ctx.createRadialGradient(x, y, innerRadius, x, y, outerRadius);
gradient.addColorStop(0, 'red');
gradient.addColorStop(1, 'transparent');
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.fillStyle = gradient;
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
ctx.stroke();
Answer is quite simple : to avoid any gradient, just build several steps having same start and end color like in :
0.0 red // first red step
0.5 red // end of first red step
0.5 blue // second blue step
1.0 blue. // end of blue step
With this idea, your code becomes :
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var x = 100,
y = 75,
innerRadius = 1,
outerRadius = 50,
radius = 60;
var gradient = ctx.createRadialGradient(x, y, innerRadius, x, y, outerRadius);
gradient.addColorStop(0, 'red');
gradient.addColorStop(0.6, 'red');
gradient.addColorStop(0.6, 'transparent');
gradient.addColorStop(1, 'transparent');
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.fillStyle = gradient;
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
ctx.stroke();
<canvas id='canvas'></canvas>
Add these
gradient.addColorStop(0.2, 'red');
gradient.addColorStop(0.2, 'transparent');
http://jsfiddle.net/8tdz0bo4/3/

In Chrome, Canvas Arcs are distorted when scaled up

The canvas context.arc() method draws distorted arcs when the context is scaled up. It looks like the arcs are (poorly) approximated with a Bézier curve. Works correctly in Firefox. Untested in IE.
I observed this problem some time ago, but recently it seems to have become much worse (I'm not sure when).
I found a number of canvas issues on StackOverflow, but not this one. If you know it to be a manifestation of an already-reported issue, please forward a link. I've already reported it via Chrome's Help/Report Issue mechanism.
Before I write my own, does anyone have a workaround? ...perhaps an overloaded or alternative 'arc' method?
The following demo is viewable here: http://keveney.com/chrome_arc_bug.html
paint_canvas();
// simulate circle with line segments
//
function regular_polygon(ctx, segments, cx, cy, r) {
var i, a;
ctx.moveTo(cx + r, cy);
for (i = 0; i < segments; i++) {
a = (Math.PI * 2) * i / segments;
ctx.lineTo(cx + r * Math.cos(a), cy + r * Math.sin(a));
}
ctx.closePath();
ctx.stroke();
}
function paint_canvas() {
var ctx;
// draw unscaled circle using canvas 'arc' method
//
ctx = document.getElementById('canv').getContext('2d');
ctx.beginPath();
ctx.strokeStyle = "#000";
ctx.lineWidth = 1.25;
ctx.arc(250, 250, 200, 0, 2 * Math.PI, false);
ctx.stroke();
// draw enclosing polygons
//
ctx.beginPath();
ctx.strokeStyle = "#c00";
regular_polygon(ctx, 36, 250, 250, 215);
regular_polygon(ctx, 36, 250, 250, 185);
// the same but scaled up from smaller units
//
ctx = document.getElementById('canv2').getContext('2d');
ctx.beginPath();
ctx.strokeStyle = "#000";
ctx.scale(100, 100);
ctx.lineWidth = 1.25 / 100;
ctx.arc(2.5, 2.5, 2, 0, 2 * Math.PI, false);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = "#c00";
regular_polygon(ctx, 36, 2.5, 2.5, 2.15);
regular_polygon(ctx, 36, 2.5, 2.5, 1.85);
}
body {
background-color: #F4F4F4;
width: 800px;
margin-left: auto;
margin-right: auto;
}
canvas {
background-color: #FFFFFF;
}
<p>Chrome arc scaling bug</p>
<canvas id="canv" height=500 width=500></canvas>
<canvas id="canv2" height=500 width=500></canvas>
<p>expected: Both images should be identical.</p>
<p>actual: Arc in second image is badly distorted.</p>
<p>Issue reported 6/17/2015.</p>
<p>tested with 43.0.2357.124 (64-bit)</p>
<p>This issue was observed some time ago, but has gotten worse in recent releases of Chrome. Not tested on Internet Explorer. If you find a convenient solution,
please notify Matt Keveney, matt#keveney.com</p>
This effect stems from an approximation of a circle with a small radius, it looks more like a square than a circle.
If you knowingly will make this kind of circles, I'd recommend making a function that draws a circle with a radius which will generate a good approximation of a circle that will scale well (I chose a radius of 10 in my example below), then adjust the parameters to achieve the wanted circle.
function drawSmallArc(x,y,r,scale) {
var adjust = 10/r;
ctx.save();
ctx.beginPath();
ctx.strokeStyle = "#00f";
ctx.scale(scale/adjust, scale/adjust);
ctx.lineWidth = 1.25 / scale * adjust;
ctx.arc(x*adjust, y*adjust,r*adjust,0,2 * Math.PI, false);
ctx.stroke();
ctx.restore();
}
In action below
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
//Two referense circles.
ctx.beginPath();
ctx.strokeStyle = "#0f0"; //green
ctx.lineWidth = 1.25;
ctx.arc(250, 250, 180, 0, 2 * Math.PI, false);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = "#0f0"; //green
ctx.lineWidth = 1.25;
ctx.arc(250, 250, 220, 0, 2 * Math.PI, false);
ctx.stroke();
//Red circle using OP's original circle
ctx.save();
ctx.beginPath();
ctx.strokeStyle = "#f00"; //Red
ctx.lineWidth = 1.25 / 100;
ctx.scale(100,100);
ctx.arc(2.5, 2.5, 2, 0, 2 * Math.PI, false);
ctx.stroke();
ctx.restore();
//blue circle with better approximation of circle.
drawSmallArc(2.5,2.5,2,100);
function drawSmallArc(x,y,r,scale) {
var adjust = 10/r;
ctx.save();
ctx.beginPath();
ctx.strokeStyle = "#00f";
ctx.scale(scale/adjust, scale/adjust);
ctx.lineWidth = 1.25 / scale * adjust;
ctx.arc(x*adjust, y*adjust,r*adjust,0,2 * Math.PI, false);
ctx.stroke();
ctx.restore();
}
<canvas id="canvas" height=500 width=500></canvas>
I have worked around the problem for now, using a high-count polygon. Mine is not a fully-compatible drop-in replacement for arc, so it will not be repeated here. It is much like the function used in the above sample code to render the red reference polygons.
I remain interested in a better solution, or a Chrome update that fixes the problem, in case anyone finds it.

How to divide a circle into three equal parts with HTML5 canvas?

How can I divide a circle into three equal parts with HTML5 canvas 2D context API like above figure?
I was trying this
Can somebody suggest a better way? probably with percentages (or in degrees) instead of hard-coded coordinates?
var can = document.getElementById('mycanvas');
var ctx = can.getContext('2d');
ctx.fillStyle = "#BD1981";
ctx.beginPath();
ctx.arc(200, 200, 150, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
ctx.strokeStyle = "#FFC8B2";
ctx.lineWidth = "2";
ctx.beginPath();
ctx.moveTo(200, 200);
ctx.lineTo(100, 100);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(200, 200);
ctx.lineTo(350, 200);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(200, 200);
ctx.lineTo(100, 300);
ctx.closePath();
ctx.stroke();
Here is a function (demo) that allows you to specify a starting point, the length and the angle in degrees:
var drawAngledLine = function(x, y, length, angle) {
var radians = angle / 180 * Math.PI;
var endX = x + length * Math.cos(radians);
var endY = y - length * Math.sin(radians);
ctx.beginPath();
ctx.moveTo(x, y)
ctx.lineTo(endX, endY);
ctx.closePath();
ctx.stroke();
}
Putting it all together (using #phant0m's drawAngledLine):
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
var RADIUS = 70;
function drawCircle(x, y, r) {
ctx.beginPath();
ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.stroke();
}
function drawAngledLine(x, y, length, angle) {
var radians = angle / 180 * Math.PI;
var endX = x + length * Math.cos(radians);
var endY = y - length * Math.sin(radians);
ctx.beginPath();
ctx.moveTo(x, y)
ctx.lineTo(endX, endY);
ctx.closePath();
ctx.stroke();
}
drawCircle(140, 140, RADIUS);
drawAngledLine(140, 140, RADIUS, 1 * (360 / 3));
drawAngledLine(140, 140, RADIUS, 2 * (360 / 3));
drawAngledLine(140, 140, RADIUS, 3 * (360 / 3));
Demo here:
http://jsfiddle.net/My8eX/
I know you probably got your answer but I found Wayne's jsfiddle helpful so I'm adding my contribution which lets you set a custom number of sections you want to divide the circle into.
http://jsfiddle.net/yorksea/3ef0y22c/2/
(also using #phant0m's drawAngledLine)
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
var RADIUS = 300;
var num_sections = 19; //set this for number of divisions
function drawCircle(x, y, r) {
ctx.beginPath();
ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.stroke();
}
function drawAngledLine(x, y, length, angle) {
var radians = angle / 180 * Math.PI;
var endX = x + length * Math.cos(radians);
var endY = y - length * Math.sin(radians);
ctx.beginPath();
ctx.moveTo(x, y)
ctx.lineTo(endX, endY);
ctx.closePath();
ctx.stroke();
}
//draw circle outline
drawCircle(320, 320, RADIUS);
//loop the number of sections to draw each
for (i = 1; i <= num_sections; i++) {
drawAngledLine(320, 320, RADIUS, i * (360 / num_sections));
}
<canvas id="canvas" width="650" height="650"></canvas>