Drawing a hollow circle without a line border - javascript

Has anyone got experience on drawing a hollow circle with Pixi?, my current approach only works in the WebGL renderer which makes me think it's flawed, and i also need to fallback to the other rendered for other devices.
this.mathPiDub = 2 * Math.PI;
this.color = 0xFF00FF
var nuRad = 10;
this.pixiCircle.beginFill(this.color, 1);
this.pixiCircle.arc(0, 0, nuRad - 2, 0, this.mathPiDub, false);
this.pixiCircle.arc(0, 0, nuRad, 0, this.mathPiDub, false);
this.pixiCircle.endFill();
The 2D rendered gives me a full circle while the WebGL cuts the hole through.
What's the best approach?

The easiest way would be to use the built in drawCircle() with a lineStyle instead of a fill.
this.pixiCircle = new PIXI.Graphics();
this.pixiCircle.lineStyle(2, 0xFF00FF); //(thickness, color)
this.pixiCircle.drawCircle(0, 0, 10); //(x,y,radius)
this.pixiCircle.endFill();
stage.addChild(this.pixiCircle);
This will work for both WebGL and Canvas renderers

If you are using version 5, you can use the beginHole() and endHole() methods.
function createHollowCircle(radius, x_coordinate, y_coordinate) {
let circle = new Graphics();
circle.beginFill(color);
circle.drawCircle(0, 0, radius);
circle.beginHole();
circle.drawCircle(0, 0, radius - 0.1);
circle.endHole();
circle.endFill();
circle.x = x_coordinate;
circle.y = y_coordinate;
return circle;
}
Hollow Circles

Related

How to draw lines unaffected by scaling and zooming

I'm trying to build an app where user can add various objects (rectangles, circles) and he can use mouse wheel to zoom-in and zoom-out.
For this zooming I set up event handler like this:
TheCanvas.on('mouse:wheel', function(options){
var p = new fabric.Point(
options.e.clientX,
options.e.clientY
);
var direction = (options.e.deltaY > 0) ? 0.9 : 1.1;
var newZoom = TheCanvas.getZoom() * direction;
// restrict too big/small zoom here:
if ((newZoom > 50) || (newZoom < 0.7)) return false;
TheCanvas.zoomToPoint( p, newZoom );
}
Everything worked fine until now. Now I want to draw a crosshair over all objects on the canvas. Something like this:
So I made my own custom object like:
CrossHairClass = fabric.util.createClass(fabric.Object, {
strokeDashArray: [1,2], // I want lines to be dashed
........
My problem is:
When user zooms with the mouse wheel, my cross-hair lines zoom their thickness too and also small dashes get bigger. But I don't want that. I want my cross-hair lines be a "hair" lines = ideally 1 pixel thick all the time regardless zoom factor of the canvas. And fine dashed line too.
Render function of my Class:
_render: function (ctx) {
// I tried it like this
var zoom = TheCanvas.getZoom();
var scale = (1/zoom) * 3.333; // with this scale it visually looked the best
// I have to scale it in X and Y while I want small dashes to stay small and also thickness of the line to stay "hair-line"
this.scaleX = this.scaleY = scale;
this.width = CROSSHAIR_SIZE / scale; // my constant from elsewhere
ctx.lineWidth = 1;
ctx.beginPath();
// this example is for horizontal line only
ctx.moveTo(-this.width / 2, 0);
ctx.lineTo(this.width / 2, 0);
this._renderStroke(ctx);
}
I tried various combinations of multiplying or dividing by scale factor or zoom factor but if I finally had lines thin, I couldn't keep their size, which must be constant (in pixels) regardless of canvas zoom. Please help.
P.S.: now I got an idea. Maybe I should create another canvas, over my current canvas and draw this crosshair on the upper canvas, which will not zoom?
EDIT 1
Based on the answer from #andreabogazzi I tried various approaches, but this finally worked out! Thanks! :)
_render: function (ctx) {
var zoom = TheCanvas.getZoom();
// ctx.save(); // this made no difference
// ctx.setTransform(1/zoom, 0, 0, 1/zoom, 0, 0); // this didn't work
this.setTransformMatrix([1/zoom, 0, 0, 1/zoom, 0, 0]);
ctx.strokStyle = 'red';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(-this.widthHalf, 0); // widthHalf computed elsewhere
ctx.lineTo(this.widthHalf, 0);
this._renderStroke(ctx); // I use this instead of ctx.stroke() while this ensures my line is still nicely dashed
// ctx.restore(); // this made no difference
}
Since you created a custom class, you have to invert the zoom of your canvas before drawing.
On the _render function of your subclass, since you should be positioned in the center of your crosshair, apply a transform matrix of scale type, with scale factor of 1/zoomLevel and everything should work.
I would say the correct way is:
_render: function (ctx) {
var zoom = TheCanvas.getZoom();
ctx.save(); // this is done anyway but if you add custom ctx transform is good practice to wrap it in a save/restore couple
ctx.transform(1/zoom, 0, 0, 1/zoom, 0, 0);
ctx.strokStyle = 'red';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(-this.widthHalf, 0); // widthHalf computed elsewhere
ctx.lineTo(this.widthHalf, 0);
this._renderStroke(ctx); // I use this instead of ctx.stroke() while this ensures my line is still nicely dashed
ctx.restore(); // this is done anyway but if you add custom ctx transform is good practice to wrap it in a save/restore couple
}
Now it happens that this object get cached from the fabricJS cache system that will probably create the cache depending on the canvas zoom too.
I have no understanding of the final use of this object, but you should include this calculation also in the cache canvas size calculation.

Display bottom canvas with a shadow effect - shadow within clipped area

I`m new with canvas so thanks for your patience.
I wrote an engine that is creating 2 different layers in 2 canvas elements which are one over another. They contain some generated pictures, which aren`t important here.
I'm trying to create an effect which will display bottom layer when I move mouse over the top layer and click.
Something like this:
This is what I have tried so far:
To use transparency on canvas element and display bottom canvas (fast but not usable)
Re-create a clipping region.
Whenever I press the mouse I store current coordinates and re-render the canvas with updated clipping region
Updating clipping region is slow if I use stroke to create shadows + I`m not sure how to remove lines from it (see picture).
If I remove shadow effect, it works really fast, but I need to have it.
The only thing that comes on my mind how to speed this, is to save coordinates of every click, and then to re-calculate that into 1 shape and drop a shadow on it - I`ll still have lines, but it will be faster because there won`t be thousand of circles to draw...
Any help will be most appreciated!
You can take advantage of the browser's built in interpolation by using it as a pseudo low-pass filter, but first by painting it black:
Copy the top layer to the bottom layer
Set source-in comp. mode
Draw all black
Set source-in comp. mode
Scale down image to 25%
Scale the 25% region back up to 50% of original (or double of current)
Scale the now 50% region back up to 100% of original. It will be blurred.
Depending on how much blur you want you can add additional steps. That being said: blurred shadow is an intensive operation no matter how it is twisted and turned. One can make compromise to only render the shadow on mouse up for example (as in the demo below).
Example
Example using two layers. Top layer let you draw anything, bottom will show shadow version at the bottom later while drawing.
var ctx = document.getElementById("top").getContext("2d"),
bctx = document.getElementById("bottom").getContext("2d"),
bg = new Image(),
isDown = false;
bg.src = "http://i.imgur.com/R2naCpK.png";
ctx.fillStyle = "#27f";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.globalCompositeOperation = "destination-out"; // "eraser"
ctx.canvas.onmousedown = function(e) {isDown = true};
window.onmousemove = function(e) {
if (!isDown) return;
var pos = getPos(ctx.canvas, e);
ctx.beginPath();
ctx.moveTo(pos.x + 10, pos.y);
ctx.arc(pos.x, pos.y, 10, 0, 2*Math.PI); // erase while drawing
ctx.fill();
};
window.onmouseup = function(e) {
if (isDown) {
isDown = false;
makeShadow();
}
};
function makeShadow(){
var w = bctx.canvas.width,
h = bctx.canvas.height,
offset = 7,
alpha = 0.75;
// reset alpha
bctx.globalAlpha = 1;
// normal comp mode to clear as it is faster than using "copy"
bctx.globalCompositeOperation = "source-over";
bctx.clearRect(0, 0, w, h);
// copy top-layer to bottom-layer
bctx.drawImage(ctx.canvas, 0, 0);
// comp. mode will only draw in to non-alpha pixels next
bctx.globalCompositeOperation = "source-in";
// black overlay
bctx.fillRect(0, 0, w, h);
// copy mode so we don't need an extra canvas
bctx.globalCompositeOperation = "copy";
// step 1: reduce to 50% (quality related - create more steps to increase blur/quality)
bctx.drawImage(bctx.canvas, 0, 0, w, h, 0, 0, w * 0.5, h * 0.5);
bctx.drawImage(bctx.canvas, 0, 0, w * 0.5, h * 0.5, 0, 0, w * 0.25, h * 0.25);
bctx.drawImage(bctx.canvas, 0, 0, w * 0.25, h * 0.25, 0, 0, w * 0.5, h * 0.5);
// shadow transparency
bctx.globalAlpha = alpha;
// step 2: draw back up to 100%, draw offset
bctx.drawImage(bctx.canvas, 0, 0, w * 0.5, h * 0.5, offset, offset, w, h);
// comp in background image
bctx.globalCompositeOperation = "destination-over";
bctx.drawImage(bg, 0, 0, w, h);
}
function getPos(canvas, e) {
var r = canvas.getBoundingClientRect();
return {x: e.clientX - r.left, y: e.clientY - r.top};
}
div {position:relative;border:1px solid #000;width:500px;height:500px}
canvas {position:absolute;left:0;top:0}
#bottom {background:#eee}
<div>
<canvas id="bottom" width=500 height=500></canvas>
<canvas id="top" width=500 height=500></canvas>
</div>

Multiple light sources on canvas

I want to place a number of light sources on a background for a game I'm making, which works great with one light source as shown below:
This is achieved by placing a .png image above everything else that becomes more transperant towards the center, like this:
Works great for one light source, but I need another approach where I can add more and move the light sources around.
I have considered drawing a similar "shadow layer" pixel by pixel for each frame, and calculate the transparency depending of the distance to each light source. However, that would probably be very slow and I'm sure there are way better solutions to this problem.
The images are just examples and each frame will have considerably more content to move around and update using requestAnimationFrame.
Is there a light weight and simple way to achieve this? Thanks in advance!
Edit
With the help of ViliusL, I came up with this masking solution:
http://jsfiddle.net/CuC5w/1/
// Create canvas
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width = 300;
canvas.height = 300;
document.body.appendChild(canvas);
// Draw background
var img=document.getElementById("cat");
ctx.drawImage(img,0,0);
// Create shadow canvas
var shadowCanvas = document.createElement('canvas');
var shadowCtx = shadowCanvas.getContext('2d');
shadowCanvas.width = canvas.width;
shadowCanvas.height = canvas.height;
document.body.appendChild(shadowCanvas);
// Make it black
shadowCtx.fillStyle= '#000';
shadowCtx.fillRect(0,0,canvas.width,canvas.height);
// Turn canvas into mask
shadowCtx.globalCompositeOperation = "destination-out";
// RadialGradient as light source #1
gradient = shadowCtx.createRadialGradient(80, 150, 0, 80, 150, 50);
gradient.addColorStop(0, "rgba(255, 255, 255, 1.0)");
gradient.addColorStop(1, "rgba(255, 255, 255, .1)");
shadowCtx.fillStyle = gradient;
shadowCtx.fillRect(0, 0, canvas.width, canvas.height);
// RadialGradient as light source #2
gradient = shadowCtx.createRadialGradient(220, 150, 0, 220, 150, 50);
gradient.addColorStop(0, "rgba(255, 255, 255, 1.0)");
gradient.addColorStop(1, "rgba(255, 255, 255, .1)");
shadowCtx.fillStyle = gradient;
shadowCtx.fillRect(0, 0, canvas.width, canvas.height);
Another way to play with light is to use the globalCompositeOperation mode 'ligther' to ligthen things, and just use globalAlpha to darken things.
First here's an image, with a cartoon lightening on the left, and a more realistic lightening on the right, but you'd rather watch the fiddle, since it's animated :
http://jsfiddle.net/gamealchemist/ABfVj/
So how i did things :
To darken :
- Choose a darkening color( most likely black, but you can choose a red or another color to teint the result).
- choose an opacity ( 0.3 seems a good start value ).
- fillRect the area you want to darken.
function darken(x, y, w, h, darkenColor, amount) {
ctx.fillStyle = darkenColor;
ctx.globalAlpha = amount;
ctx.fillRect(x, y, w, h);
ctx.globalAlpha = 1;
}
To lighten :
- Choose a lightening color. Beware that this color's r,g,b will be added to the previous point's r,g,b : if you use a high value your color will get burnt.
- change the globalCompositeOperation to 'lighter'
- you might change opacity also, to have more control over the lightening.
- fillRect or arc the area you want to lighten.
If you draw several circles while in lighter mode, the results will add up, so you can choose a quite low value and draw several circles.
function ligthen(x, y, radius, color) {
ctx.save();
var rnd = 0.03 * Math.sin(1.1 * Date.now() / 1000);
radius = radius * (1 + rnd);
ctx.globalCompositeOperation = 'lighter';
ctx.fillStyle = '#0B0B00';
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * π);
ctx.fill();
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, radius * 0.90+rnd, 0, 2 * π);
ctx.fill();
ctx.beginPath();
ctx.arc(x, y, radius * 0.4+rnd, 0, 2 * π);
ctx.fill();
ctx.restore();
}
Notice that i added a sinusoidal variation to make the light more living.
Ligthen : another way :
You can also, while still using the 'ligther' mode, use a gradient to have a smoother effect (first one is more cartoon like, unless you draw a lot of circles.).
function ligthenGradient(x, y, radius) {
ctx.save();
ctx.globalCompositeOperation = 'lighter';
var rnd = 0.05 * Math.sin(1.1 * Date.now() / 1000);
radius = radius * (1 + rnd);
var radialGradient = ctx.createRadialGradient(x, y, 0, x, y, radius);
radialGradient.addColorStop(0.0, '#BB9');
radialGradient.addColorStop(0.2 + rnd, '#AA8');
radialGradient.addColorStop(0.7 + rnd, '#330');
radialGradient.addColorStop(0.90, '#110');
radialGradient.addColorStop(1, '#000');
ctx.fillStyle = radialGradient;
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * π);
ctx.fill();
ctx.restore();
}
i also added here a sin variation.
Rq : creating a gradient on each draw will create garbage : store the gradient if you use a single gradient, and store them in an array if you want to animate the gradients.
If you are using the same light in several places, have a single gradient built, centered on (0,0), and translate the canvas before drawing always with this single gradient.
Rq 2 : you can use clipping to prevent some parts of the screen to be lightened (if there's an obstacle).
I added the blue circle on my example to show this.
So you might want to ligthen directly your scene with those effects, or create separately a light layer that you darken/lighten as you want before drawImage it on the screen.
There are too many scenari to discuss them here (light animated or not, clipping or not, pre-compute a light layer or not, ...) but as far as speed is concerned, for Safari and iOS safari, the solution using rect/arc draws -either with gradient or a solid fill- will be rocket faster than drawing an image/canvas.
On Chrome it will be quite the opposite : it's faster to draw an image than to draw each geometry when the geometry count raises.
Firefox is rather similar to Chrome for this.
your png should have full transparent corners and not transparent white in middle.
or you can draw this, but not pixel by pixel like here jsfiddle.net/pr9r7/2/
More examples: jsfiddle.net/pr9r7/3/ http://codepen.io/cwolves/pen/prvnb
Here is my Take on it:
A. Don't worry about performance until you have tried it out. The Canvas is pretty darn fast at drawing.
B. Rather than having a image with dark Corners and a Transparent middle. Why don't you try and make it more "IRL" and have the overall world be more Dark and let the light-source illuminate the Area? Highlight a small area, instead of darken everything EXCEPT a small Area.

HTML5 canvas drop counter

Hi i have a ball dropping in a html5 canvas, i would like to have a counter that displays how far the ball is dropping.
var ball = new Kinetic.Shape(function(){
var context = this.getContext();
context.beginPath();
context.arc(0, 0, radius, 0, 2 * Math.PI, false);
context.fillStyle = "black";
context.fill();
});
Someone any idea how to do this ?
Your shape has a position on the canvas, you should clearly define it just to be sure.
But if you want to count how your object moves on the screen you can just do:
ball.getPosition();
This will return you a point in the form of
{x: number, y: number}
you can save the individual x,y values like so:
var xPosition = ball.getPosition().x;
var yPosition = ball.getPosition().y;
you can save those positions in your animation and do a sum of how much the ball moved.

How can i draw a Square in HTML5 Canvas at run time?

I am working on a HTML5 Project.There is a drawing graphics API to draw Rectangle (fillRectStrokeRect).But how can i draw a SQUARE. I have tried the following way to draw it
CODE
getMouse(e);
x2=mx; y2=my;
var width=endX-startX;
var height=endY-startY;
annCanvasContext.beginPath();
annCanvasContext.lineWidth=borderWidth;
var centerX=width/2;
var centerY=width/2;
var radius=width/2;
annCanvasContext.arc(centerX+5, centerY+5, radius, 0, 2 * Math.PI, false);
annCanvasContext.stroke();
Use fillRect or strokeRect with the width and height being equal.
var x = 0, y = 0,
side = 10;
ctx.fillRect(x, y, side, side);
Demo
As you say in the comments, if you want to fit the largest square in a circle, it's more Math related than about code. I'll trying explaining it to you, but you'll probably find better, more visual explanations elsewhere on the Internet.
Draw the diameter of the circle in a way that it divides your square into two equal parts. Now one part is a right angled triangle, which has two of its sides equal. We know the diameter. Using the Pythogorean theorem, you get this equation:
side^2 + side^2 = diameter^2.
Let's find the side now.
2(side^2) = diameter^2
side^2 = (diameter^2)/2
side = Math.sqrt( (diameter^2)/2 )
Now, to turn this into code.
var ctx = document.getElementById('canvas').getContext('2d'),
radius = 20;
ctx.canvas.addEventListener('click', function (e){
ctx.fillStyle = 'black';
ctx.arc(e.pageX, e.pageY, radius, 0, Math.PI*2, false);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = 'red';
var diameter = radius * 2;
var side = Math.sqrt( (diameter * diameter)/2 );
ctx.fillRect(e.pageX - side/2, e.pageY - side/2, side, side);
ctx.closePath();
}, false);
This would draw a square inside a circle wherever you click on the canvas.
Demo

Categories

Resources