I'm currently trying to draw arcs using the KineticJS canvas framework. The issue I'm having is that these arcs are not nearly as "perfectly" circular as required.
They're being drawn out as follows:
var redArc = new Kinetic.Shape({
drawFunc: function (canvas) {
var ctx = canvas.getContext();
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI / 2, true); // Arc of 270°
canvas.fillStroke(this);
},
x: x,
y: y,
stroke: 'rgb(255,0,0)',
strokeWidth: 20,
offset: [x, y]
});
I knew that this might have been an issue since there's no such thing as rendering a near-perfect circle on a pixel based medium without the use of anti-aliasing on the stroke
I was wandering if this issue might be resolved by rendering with vector graphics or if there was an alternative solution?
I've included a JSFiddle to outline this issue, the circles are being animated rotating around their axis. This effect is more apparent with the arc(s) appearing to "wobble" as they rotate.
JSFiddle: http://jsfiddle.net/EHDFV/
Could the wobble be an optical illusion.
Have added a stationary black circle around your animation.
var blackCircle = new Kinetic.Shape({
drawFunc: function (canvas) {
var ctx = canvas.getContext();
ctx.beginPath();
ctx.arc(x, y, (r + 10), 0, 2 * Math.PI, true);
canvas.fillStroke(this);
},
x: x,
y: y,
stroke: 'rgb(0,0,0)',
strokeWidth: 1,
offset: [x, y]
});
Does your wobble cross the black circle at any point or leave a gap?
New fiddle http://jsfiddle.net/EHDFV/1/
Related
First, I draw an arc with diameter at any position. I would then delete a circular area in another position so that it could cut the first arc, using ctx.globalCompositeOperation = 'destination-out' and draw an arc and then set ctx.globalCompositeOperation = 'source- over'. I'll end up drawing a similar arc at the exact spot I just deleted.
Its two func i used to:
const eraseDot = (ctx, { x, y, size }) => {
ctx.globalCompositeOperation = 'destination-out';
drawDot(ctx, { x, y, size, color: 'white' });
ctx.globalCompositeOperation = 'source-over';
}
const drawDot = (ctx, { x, y, size, color }) => {
ctx.beginPath();
ctx.arc(x, y, size / 2, 0, Math.PI * 2);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
};
My desired result is to draw 2 intersecting arcs (like how to draw 2 normal arcs but I want to clear the image area before drawing the 2nd arc). But it appears that there is a gap between the intersection of the 2 arcs:
enter image description here
this means different erase and draw area sizes. please tell me the reason of this and how to solve it , thanks :
How can I fill a triangle with gradients starting at its vertices given a color for each vertex?
I'm trying to reproduce something like this:
I'm making use of the built in fill function from the HTML5 canvas Context2D. I'm trying to avoid having to deal with pixel-by-pixel interpolations based on their distance to the vertices. I fear it wont be as performatic as the built-in fill function (?). Also I can't deal with WebGL right now.
I've done a trick using radial gradients, but, there are a few problems with my naive approach:
The colors don't seem to blend well
The last applied gradient overwrites the others
The value used in the radius variable is arbitrary
OBS: I don't know if it's relevant but, I'm building a triangle strip (indexed geometry actually).
var canvas = document.querySelector("canvas");
var ctx = canvas.getContext("2d");
var v1 = { x: 100, y: 0 };
var v2 = { x: 0, y: 180 };
var v3 = { x: 200, y: 180 };
var radius = 175;
var grd1 = ctx.createRadialGradient(v1.x, v1.y, 0, v1.x, v1.y, radius);
grd1.addColorStop(0, "#FF0000FF");
grd1.addColorStop(1, "#FF000000");
var grd2 = ctx.createRadialGradient(v2.x, v2.y, 0, v2.x, v2.y, radius);
grd2.addColorStop(0, "#00FF00FF");
grd2.addColorStop(1, "#00FF0000");
var grd3 = ctx.createRadialGradient(v3.x, v3.y, 0, v3.x, v3.y, radius);
grd3.addColorStop(0, "#0000FFFF");
grd3.addColorStop(1, "#0000FF00");
ctx.beginPath();
ctx.moveTo(v1.x, v1.y);
ctx.lineTo(v2.x, v2.y);
ctx.lineTo(v3.x, v3.y);
ctx.closePath();
ctx.fillStyle = "#FFFFFFFF"; // fill with white and apply the gradients on top of it
ctx.fill();
ctx.fillStyle = grd1;
ctx.fill();
ctx.fillStyle = grd2;
ctx.fill();
ctx.fillStyle = grd3;
ctx.fill();
<canvas width="200" height="180"></canvas>
The colors don't seem to blend well
For this you can use the globalCompositeOperation property of your 2D context to one of its blend modes, even though in your case the compositing mode "lighter" with a black background seems to produce the closest result to your model.
The last applied gradient overwrites the others
Thanks to the previous bullet point, it's not the case anymore.
The value used in the radius variable is arbitrary
Doesn't look like so to me, it does correspond to the distance between every points of your equilateral triangle and its center, which makes perfect sense.
var canvas = document.querySelector("canvas");
var ctx = canvas.getContext("2d");
// reordered to make the same as OP's image
var v1 = { x: 0, y: 180 };
var v2 = { x: 200, y: 180 };
var v3 = { x: 100, y: 0 };
var radius = 180;
var grd1 = ctx.createRadialGradient(v1.x, v1.y, 0, v1.x, v1.y, radius);
grd1.addColorStop(0, "#FF0000FF");
grd1.addColorStop(1, "#FF000000");
var grd2 = ctx.createRadialGradient(v2.x, v2.y, 0, v2.x, v2.y, radius);
grd2.addColorStop(0, "#00FF00FF");
grd2.addColorStop(1, "#00FF0000");
var grd3 = ctx.createRadialGradient(v3.x, v3.y, 0, v3.x, v3.y, radius);
grd3.addColorStop(0, "#0000FFFF");
grd3.addColorStop(1, "#0000FF00");
ctx.beginPath();
ctx.moveTo(v1.x, v1.y);
ctx.lineTo(v2.x, v2.y);
ctx.lineTo(v3.x, v3.y);
ctx.closePath();
// fill with black
ctx.fill();
// set blend mode
ctx.globalCompositeOperation = "lighter";
ctx.fillStyle = grd1;
ctx.fill();
ctx.fillStyle = grd2;
ctx.fill();
ctx.fillStyle = grd3;
ctx.fill();
// if you need to draw something else, don't forget to reset the gCO
ctx.globalCompositeOperation = "source-over";
<canvas width="200" height="180"></canvas>
I`m trying to draw a long (actually very long) bezier lines on my graph. When hover over line it should become thicker. And i encountered an issue. Sometimes lines has different curvature depending on lineWidth in Chrome.
Code exapmle
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.setTransform(1, 0, 0, 1, -3700, -50)
const gap = 32;
const line = [
{x: 3830, y: 95},
{x: 3830 + gap, y: 95},
{x: 12600 - gap, y: 25895},
{x: 12600, y: 25895}
];
// Draw bezier out of box
function drawLine(p1, a1, a2, p2, width, color) {
ctx.strokeStyle = color;
ctx.lineWidth = width;
ctx.beginPath();
ctx.moveTo(p1.x,p1.y);
ctx.bezierCurveTo(a1.x, a1.y, a2.x, a2.y, p2.x, p2.y);
ctx.stroke();
}
drawLine.apply(null, line.concat([3, '#f00']));
drawLine.apply(null, line.concat([1, '#00f']));
JSFiddle
In Safari it looks fine, but Chrome and FF draw the same lines with the same points different.
Looks like curvature depending on lineWidth.
May be someone knows how to solve this problem?
I'm using the verlet.js plugin in order create a cloth simulation on canvas with a texture image.
The only thing (and the most important BTW) part that I haven't arrived is that I need skew the drawImage in order to make it fit the correct position.
jsfiddle with the progress
//Drawing the rectangle
ctx.save();
ctx.beginPath();
ctx.moveTo(cloth.particles[i1].pos.x, cloth.particles[i1].pos.y);
ctx.lineTo(cloth.particles[i1+1].pos.x, cloth.particles[i1+1].pos.y);
ctx.lineTo(cloth.particles[i2].pos.x, cloth.particles[i2].pos.y);
ctx.lineTo(cloth.particles[i2-1].pos.x, cloth.particles[i2-1].pos.y);
ctx.lineTo(cloth.particles[i1].pos.x, cloth.particles[i1].pos.y);
ctx.strokeStyle = "#fff";
ctx.stroke();
ctx.restore();
//Wrapping the image
ctx.save();
var off = cloth.particles[i2].pos.x - cloth.particles[i1].pos.x;
//THIS IS WHAT I TRY TO SOLVE TO FIT TO THE RECTANGLES
//ctx.transform(1,0.5,0,1,0,0);
ctx.drawImage(img, cloth.particles[i1].pos.x,cloth.particles[i1].pos.y, off, off, cloth.particles[i1].pos.x,cloth.particles[i1].pos.y, off ,off);
ctx.restore();
}
I have tried to adapt other cloth simulations but without success. Any clue where I could find some info to accomplish that?
Using skew (or rather shear) to fill tiles only works if the cell is a parallelogram, as 2D affine transforms only support this shape.
Here is one approach:
Calculate angle of upper line
Calculate angle of left line
Calculate width and height of cell
In a parallelogram bottom line will equal upper line, and of course right line equals left line.
Then set these angles as skew arguments for the transform coupled with translate to the upper left corner.
Then just repeat for each cell.
Example
var img = new Image;
img.onload = function() {
var ctx = document.querySelector("canvas").getContext("2d"),
tile1 = [
{x: 10, y: 10}, // upper left corner
{x: 210, y: 50}, // upper right
{x: 230, y: 150}, // bottom right
{x: 30, y: 110} // bottom left
],
tile2 = [
{x: 210, y: 50},
{x: 410, y: 5},
{x: 430, y: 105},
{x: 230, y: 150}
];
renderTile(this, tile1);
renderTile(this, tile2);
function renderTile(img, tile) {
var dx, dy, a1, a2, w, h, i = 1;
// reference shape (remove this section):
ctx.setTransform(1,0,0,1,0,0);
ctx.moveTo(tile[0].x, tile[0].y);
while(i < 4) ctx.lineTo(tile[i].x, tile[i++].y);
ctx.closePath();
ctx.strokeStyle = "#0c0";
ctx.lineWidth = 2;
ctx.stroke();
// calc horizontal angle
dx = tile[1].x - tile[0].x; // horizontal diff.
dy = tile[1].y - tile[0].y; // vertical diff.
a1 = Math.atan2(dy, dx); // angle, note dy,dx order here
w = dx|0; // width based on diff for x
// calc vertical angle
dx = tile[3].x - tile[0].x;
dy = tile[3].y - tile[0].y;
a2 = Math.atan2(dx, dy); // note dx,dy order here
h = dy|0;
// draw image to fit parallelogram
ctx.setTransform(1, a1, a2, 1, tile[0].x, tile[0].y);
ctx.drawImage(img, 0, 0, w, h);
}
};
img.src = "http://i.imgur.com/rUeQDjE.png";
<canvas width=500 height=160/>
Note: if your cloth simulation produces other shapes than parallelograms (ie. quadrilaterals), which is very likely since this is a physics simulation, this approach won't work well. In that case you need different techniques which are more compute heavy. For this reason WebGL is a better fit. Just my two cents..
I'm trying to achieve the following:
A number of concentric circles (or rings) are drawn on a canvas. Each circle has a "hole" in it, so the smaller circles, drawn behind it are partially visible. Each frame (we're using window.requestAnimationFrame to render) the radius of each circle/shape/ring is slightly increased.
A scenario with two rings is depicted in the image here.
The code:
function draw() {
drawBgr();
for (var i = 0, len = rings.length; i < len; i++) {
rings[i].draw();
}
}
function drawBgr() {
context.globalCompositeOperation = "source-over";
context.clearRect(0, 0, WIDTH, HEIGHT);
context.rect(0, 0, WIDTH, HEIGHT);
context.fillStyle = '#FFFFFF';
context.fill();
}
function squareRing(ring) { //called by rings[i].draw();
context.globalCompositeOperation = "source-over";
context.fillRect(ring.centerX - ring.radius / 2, ring.centerY - ring.radius / 2, ring.radius, ring.radius);
context.globalCompositeOperation = "source-out";
context.beginPath();
context.arc(CENTER_X, CENTER_Y, ring.radius, 0, 2 * Math.PI, false);
//context.lineWidth = RING_MAX_LINE_WIDTH * (ring.radius / MAX_SIDE);
context.fillStyle = '#000000';
context.fill();
context.globalCompositeOperation = "source-over";
}
What exactly is the problem here? I'm calling clearRect before the circles are drawn. See "What I'm actually getting" image. This is the result of a SINGLE RING being drawn over a number of frames. I shouldn't be getting anything different than a black circle with a hollow square in the middle. (Note that radius is increasing each frame.)
I do realize switching globalCompositeOperation might not suffice for the effect I desire. How can I draw a "hole" in an object drawn on the canvas without erasing everything in the "hole" underneath the object I'm trying to modify?
This is the tutorial I used as a reference for the globalCompositeOperation values.
I'm using Firefox 28.0.
I would not try to use globalCompositeOperation, since i find it hard to figure out what will happen after several iterations, and even harder if the canvas was not cleared before.
I prefer to use clipping, which gets me to that :
http://jsbin.com/guzubeze/1/edit?js,output
So, to build a 'hole' in a draw, how to use clipping ?
-->> Define a positive clipping sub-path, and within this area, cut off a negative part, using this time a clockwise sub-path :
Clipping must be done with one single path, so rect() cannot be used : it does begin a path each time, and does not allow to choose clockwisity (:-)), so you have to define those two functions which will just create the desired sub-paths :
// clockwise sub-path of a rect
function rectPath(x,y,w,h) {
ctx.moveTo(x,y);
ctx.lineTo(x+w,y);
ctx.lineTo(x+w,y+h);
ctx.lineTo(x,y+h);
}
// counter-clockwise sub-path of a rect
function revRectPath(x,y,w,h) {
ctx.moveTo(x,y);
ctx.lineTo(x,y+h);
ctx.lineTo(x+w,y+h);
ctx.lineTo(x+w,y);
}
then you can write your drawing code :
function drawShape(cx, cy, d, scale, rotation) {
ctx.save();
ctx.translate(cx,cy);
scale = scale || 1;
if (scale !=1) ctx.scale(scale, scale);
rotation = rotation || 0;
if (rotation) ctx.rotate(rotation);
// clip with rectangular hole
ctx.beginPath();
var r=d/2;
rectPath(-r,-r, d, d);
revRectPath(-0.25*r,-0.8*r, 0.5*r, 1.6*r);
ctx.closePath();
ctx.clip();
ctx.beginPath();
// we're clipped !
ctx.arc(0,0, r, 0, 2*Math.PI);
ctx.closePath();
ctx.fill();
ctx.restore();
}
Edit :
For the record, there is a simpler way to draw the asked scheme : just draw a circle, then draw counter clockwise a rect within. What you fill will be the part inside the circle that is outside the rect, which is what you want :
function drawTheThing(x,y,r) {
ctx.beginPath();
ctx.arc(x ,y, r, 0, 2*Math.PI);
revRectPath(x-0.25*r, y-0.8*r, 0.5*r, 1.6*r);
ctx.fill();
ctx.closePath();
}
(i do not post image : it is the same).
Depending on your need if you change the draw or if you want to introduce some kind of genericity, use first or second one.
If you do not change the scheme later, the second solution is simpler => better.