Related
I've created a program to generate planet sprites. I'm doing that by creating a circular path, running ctx.clip() to keep all the following layers inside of the circle, then drawing a black and transparent texture layer, then a randomly colored rectangle over the full canvas, then a shadow and glow on top of it all. The issue is that a colored line also appears under the circle after clipping, and I'm not sure why. I need this removed.
Here is a fiddle. The last line sets the code to loop every half second: https://jsfiddle.net/tzkwmzqu/4/
I am not sure I do understand your problem, but I will assume that you are talking about the anti-aliasing problem.
Currently, you are drawing a lot over your clipped area.
At each draw, new anti-aliasing artifacts will come to smooth the latest drawing. At the end, what should have been semi-transparent pixels are now fully opaque ones.
In the other hand, with globalCompositeOperation like 'destination-in', you need only one drawing to make the compositing (~clipping). So you don't accumulate artifacts. But even if you did, gCO is global and since it takes transparency into account, the accumulation would be less important.
var ctx1 = clip.getContext('2d');
var ctx2 = gCO.getContext('2d');
var ctx3 = gCO2.getContext('2d');
ctx1.beginPath();
ctx1.arc(150, 150, 150, 0, Math.PI*2)
ctx1.clip();
// drawing multiple times on this clipped area will increase artifacts
ctx1.fillRect(0,0,300, 150);
ctx1.fillRect(0,0,300, 150);
ctx1.fillRect(0,0,300, 150);
ctx1.fillRect(0,0,300, 150);
ctx2.beginPath();
ctx2.arc(150, 150, 150, 0, Math.PI*2)
ctx2.fillRect(0,0,300, 150);
ctx2.globalCompositeOperation = 'destination-in';
//With gCO you only draw once, but even if you did draw multiple times, there would still be less artifacts
ctx2.fill();
ctx2.fill();
ctx2.fill();
ctx2.fill();
ctx2.globalCompositeOperation = 'source-over';
ctx3.beginPath();
ctx3.arc(150, 150, 150, 0, Math.PI*2)
ctx3.fillRect(0,0,300, 150);
ctx3.globalCompositeOperation = 'destination-in';
// only one drawing needed:
ctx3.fill();
ctx3.globalCompositeOperation = 'source-over';
ctx1.fillStyle = ctx2.fillStyle = ctx3.fillStyle = "white";
ctx1.fillText('clipping', 120, 100);
ctx2.fillText('compositing', 120, 100);
ctx3.fillText('single compositing', 120, 100);
canvas{
border: 1px solid;
}
<canvas id="clip"></canvas><canvas id="gCO"></canvas><canvas id="gCO2"></canvas>
A few unrelated notes about your code :
closePath does not mark the end of your path declaration, only a new beginPath() call does. ctx.fillStyle = 'transparent'; ctx.fill() won't do anything. Only putImageData, clearRect methods and globalCompositeOperation + drawing method can produce transparent pixels.
So here is all the above in one snippet :
/* Load images */
var texture = new Image();
texture.src = "http://i.imgur.com/0qMwa8p.png";
var shadow = new Image();
shadow.src = "http://i.imgur.com/pX3HVFY.png";
/* Create the canvas and context references */
var canvas = document.getElementById("game");
canvas.style.width = (canvas.width = 512) + "px";
canvas.style.height = (canvas.height = 512) + "px";
var ctx = canvas.getContext("2d");
/* render */
function render() {
/* Size of planets */
var scale = Math.random() + 1
// We don't need to save/restore the canvas state now,
// simply remember to set the gCO back to 'source-over'
// here it done at the end of the function
/* Clear canvas for redraw */
ctx.clearRect(0, 0, canvas.width, canvas.height);
/* Place texture onto planet */
ctx.globalAlpha = Math.random() * .5 + .5;
ctx.drawImage(texture, (Math.round(Math.random() * 256) - 128 * scale), (Math.round(Math.random() * 256) - 128 * scale), texture.naturalWidth * scale, texture.naturalHeight * scale)
/* Color Planet */
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "multiply";
var color = "hsl(" + Math.random() * 256 + ", 100%, 50%)"
ctx.fillStyle = color;
ctx.fillRect(0, 0, canvas.width, canvas.height)
/* Give planet its shine and shadow */
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(shadow, Math.round(Math.random() * 200 - 128 * scale), Math.round(Math.random() * 200 - 128 * scale), shadow.naturalWidth * scale, shadow.naturalHeight * scale)
// instead of clipping, use gCO
ctx.globalCompositeOperation = 'destination-in';
ctx.beginPath();
ctx.arc(256, 256, 128 * scale, 0, 2 * Math.PI);
ctx.fill();
// reset gCO
ctx.globalCompositeOperation = 'source-over';
}
render()
window.interval = setInterval(render, 500)
#game {
border: 1px solid black;
background-color: black;
}
<canvas id="game"></canvas>
My problem is i have a model (g3db) and
touchPosition.x = Gdx.input.getX();
touchPosition.y = Gdx.input.getY();
rotation.y = (float) Math.atan2(touchPosition.x - Gdx.graphics.getWidth() / 2, touchPosition.y - Gdx.graphics.getHeight() / 2) * MathUtils.radiansToDegrees + 90;
shipInstance.transform.translate(-10, 0, 0);
shipInstance.transform.setToRotation(new Vector3(0,1,0),rotation.y);
the ship is only rotating but not moving in the space.
What did i wrong?
I have a triangle in canvas and I want to fill it with color .. maybe more than one color .. how I can do that ?
my triangle is here :
function draw() {
var width = 360; // Triangle Width
var height = 400; // Triangle Height
var padding = 90;
// Draw a path
ctx.beginPath();
ctx.moveTo(padding + width/2, padding+height); // Top Corner
ctx.lineTo(padding + width, padding); // TOP Right
ctx.lineTo(padding, padding); // TOP Left
ctx.closePath();
ctx.strokeStyle = "#e8ecef";
ctx.lineWidth = 25;
ctx.stroke();
ctx.restore();
}
the current result is like the one in this image :
the result I want something like this :
please any help ? how I can do this ? or if there is any plugin will help me to do this faster ?
Composition modes
The problem can be solved simply by using a couple of composition modes and very little math (only to calculate correct position).
The receipt would be:
Draw a mask representing the full triangle background (color does not matter as long as it is opaque)
Create a gradient with hard transitions to define different color sections.
Composite a rectangle on top which is clipped to background shape using source-in mode.
Stroke the border using the normal source-over mode
All these steps can reuse a single path. The gradient will allow you to define all colored sections in one place. Just make sure each section starts and end with the same color and that the next section will start from where the previous stopped. This will make a hard edge between the sections.
Since the gradient color stops takes normalized values, it becomes a simple task to match any size simply by defining the gradient line's start and stop points relative to the triangle's position and size.
Adjust as needed.
Example with modified code
I inserted the code in your base code to show where the modifications needs to be.
var ctx = c.getContext("2d"),
width = 360, height = 400, padding = 90, split = 0.33;
// Draw a path that will be reused below
ctx.beginPath();
ctx.moveTo(padding + width/2, padding+height); // Top Corner
ctx.lineTo(padding + width, padding); // TOP Right
ctx.lineTo(padding, padding); // TOP Left
ctx.closePath();
// fill for a mask (color doesn't matter as long as it's opaque)
ctx.fill();
// define gradient
var gr = ctx.createLinearGradient(0, padding, 0, padding+height); // line
gr.addColorStop(0 , "rgb(187, 19, 221)");
gr.addColorStop(split, "rgb(187, 19, 221)");
gr.addColorStop(split, "rgb(38, 199, 222)");
gr.addColorStop(1 , "rgb(38, 199, 222)");
// fill the colored sections (adjust positions as needed)
ctx.globalCompositeOperation = "source-in";
ctx.fillStyle = gr;
ctx.fillRect(0, 0, c.width, c.height);
// stroke outline
ctx.globalCompositeOperation = "source-over";
ctx.strokeStyle = "#e8ecef";
ctx.lineWidth = 25;
ctx.stroke();
<canvas id=c width=500 height=550></canvas>
Effectively, a good way to do this is to draw a base triangle and then a trapezium over the top of this, and then do the stroke over these.
function draw(percent) {
var width = 360; // Triangle Width
var height = 400; // Triangle Height
var padding = 90;
// Draw the purple triangle underneath
ctx.beginPath();
ctx.moveTo(padding + width/2, padding+height); // Top Corner
ctx.lineTo(padding + width, padding); // TOP Right
ctx.lineTo(padding, padding); // TOP Left
ctx.fillStyle = "#990099";
ctx.fill();
// Draw second triangle/trapezium over the top
ctx.beginPath();
ctx.moveTo(padding + width*percent/2, padding+height*percent); //Bottom left
ctx.lineTo(padding + width - width*percent/2, padding+height*percent); //Bottom right
ctx.lineTo(padding + width, padding); // TOP Right
ctx.lineTo(padding, padding); // TOP Left
ctx.fillStyle = "#00ccff";
ctx.fill();
// Draw the grey line around the triangles
ctx.beginPath();
ctx.moveTo(padding + width/2, padding+height); // Top Corner
ctx.lineTo(padding + width, padding); // TOP Right
ctx.lineTo(padding, padding); // TOP Left
ctx.closePath();
ctx.strokeStyle = "#e8ecef";
ctx.lineWidth = 25;
ctx.stroke();
ctx.restore();
}
draw(0.1);
The draw function works with any value between 0 and 1, where 0 is 0% blue and 1 is 100% blue. As this is a triangle, the area filled will not appear equal if at 50%, however the percentage will be halfway between the top and bottom coordinates.
I hope this helps
Triangles and area
To solve for fraction (p) (0-1) to give new height (h1) for a triangle (w) width and (h) height
Area of triangle is
w * h * 0.5
Cutting the triangle will give a triangle and a trapezoid.
We will solve so that the fraction area is the area of the trapezoid.
The area of a trapezoid (the other end to the pointy end) in terms of the triangle w,h and h1 where h1 is the unknown height of the trapezoid.
a1 = w * h1 - (w / 2 * h) * h1 * h1
The h1 * h1 means this solution will be a quadratic so make a formula that equates to 0. We know the area of the trapezoid is the triangle area times the fraction, so use that (w * h * 0.5 * p) and subtract the above area solution in terms of h1 to give
0 = w * h * 0.5 * p - w * h1 - (w / 2 * h) * h12
This gives a quadratic the general form is ax2 + bx + c = 0 (in this case the unknown value x is h1 the height of the trapezoid) which can be solved with (-b (+/-) sqrt(b2 - 4ac) ) / 2a. Because of the (+/-) in the solution there are two answers, at this stage it is unknown which answer is correct.
thus to solve we need a,b,c which are
a = w/(2*h)
b = - w
c = w * h * 0.5 * p
So as a function
// return the height of the trapezoid made from a triangle of width height that
// has an area equal to the area of the triangle * fraction
function getFractionHeightOfTriangle(width,height,fraction){
var a = width / (2 * height);
var b = -width;
var c = width * height * 0.5 * fraction;
// find the two solutions
var h1_a = (-b + Math.sqrt(b * b - 4 * a * c)) / (2 * a);
var h1_b = (-b - Math.sqrt(b * b - 4 * a * c)) / (2 * a);
// from experiment the solution is the second one
// or you can check which is positive and return that
return h1_b;
}
To draw a triangle using the top as the origin
// x,y is the top (pointy end) w is width h is height
// col is stroke colour, col1 is fillColour
// lineW is line width.
var drawTriangle = function(x,y,w,h,col,col1,lineW){
ctx.beginPath();
ctx.strokeStyle = col;
ctx.fillStyle = col1;
ctx.lineWidth = lineW;
ctx.moveTo(x,y );
ctx.lineTo(x + w / 2 ,y + h);
ctx.lineTo(x - w / 2 ,y + h);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
To draw part of a triangle
// See above function for x,y,w,h,col,col1,lineW
// which is == "top" for pointy end and != "top" for trapezoid
// amount is the height of the top
var drawPartTriangle = function(x,y,w,h,which,amount,col,col1,lineW){
ctx.beginPath();
ctx.strokeStyle = col;
ctx.fillStyle = col1;
ctx.lineWidth = lineW;
if(which === "top"){
ctx.moveTo(x,y);
ctx.lineTo(x + w / 2 * (amount / h),y + amount);
ctx.lineTo(x - w / 2 * (amount / h),y + amount);
}else{
ctx.moveTo(x + w / 2 * (amount / h),y + amount);
ctx.lineTo(x + w / 2 ,y + h);
ctx.lineTo(x - w / 2 ,y + h);
ctx.lineTo(x - w / 2 * (amount / h),y + amount);
}
ctx.closePath();
ctx.fill();
ctx.stroke();
}
I am trying to create a sample circle to test the pixel per meter.
My PPM is set to 100.
Height and width was set to 320 and 480
Do you why It won't center in y axis but it will center in x axis. also when I set the radius to 50 it doens't draw anything. Am I missing something here?
this.world = new World(new Vector2(0.0f, -9.8f), true);
BodyDef bodyDef = new BodyDef();
bodyDef.position.set( App.WIDTH / 2 / PPM, App.HEIGHT / 2 / PPM);
bodyDef.type = BodyDef.BodyType.StaticBody;
Body body = world.createBody(bodyDef);
FixtureDef fixtureDef = new FixtureDef();
CircleShape shape = new CircleShape();
shape.setRadius(100 / PPM);
fixtureDef.shape = shape;
body.createFixture(fixtureDef);
shape.dispose();
player = new Player(body);
box2DDebugRenderer = new Box2DDebugRenderer();
box2dCam = new OrthographicCamera();
box2dCam.setToOrtho(false, App.WIDTH / PPM , App.HEIGHT / PPM );
When Radius is 100, when radius is 50 the screen is just plain black
Nevermind guys.
shape.setRadius((float) 20 / PPM); I had to cast it to float to show all lower than 100
PPM was declared int should be float to avoid casting.
to center it on screen
box2dCam.position.set(App.WIDTH / 2 / PPM, App.HEIGHT / 2 / PPM, 0);
Basically I want to be able to Fill a Circle using canvas, but it animate to a certain percentage.
I.e only have the circle fill up 80% of the way.
My canvas knowledge isn't amazing, Here is an image i made in photoshop to display what i want.
I want the circle to start empty and then Fill up to say 70% of the circle.
Is this possible with Canvas, if so? can anyone shed some light on how to do it?
Here is a fiddle of what I've managed
http://jsfiddle.net/6Vm67/
var canvas = document.getElementById('Circle');
var context = canvas.getContext('2d');
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
var radius = 80;
context.beginPath();
context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
context.fillStyle = '#13a8a4';
context.fill();
context.lineWidth = 10;
context.strokeStyle = '#ffffff';
context.stroke();
Any help would be massively appreciated
Clipping regions make this very easy. All you have to do is make a circular clipping region and then fill a rectangle of some size to get a "partial circle" worth of fill. Here's an example:
var canvas = document.getElementById('Circle');
var context = canvas.getContext('2d');
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
var radius = 80;
var full = radius*2;
var amount = 0;
var amountToIncrease = 10;
function draw() {
context.save();
context.beginPath();
context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
context.clip(); // Make a clipping region out of this path
// instead of filling the arc, we fill a variable-sized rectangle
// that is clipped to the arc
context.fillStyle = '#13a8a4';
// We want the rectangle to get progressively taller starting from the bottom
// There are two ways to do this:
// 1. Change the Y value and height every time
// 2. Using a negative height
// I'm lazy, so we're going with 2
context.fillRect(centerX - radius, centerY + radius, radius * 2, -amount);
context.restore(); // reset clipping region
context.beginPath();
context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
context.lineWidth = 10;
context.strokeStyle = '#000000';
context.stroke();
// Every time, raise amount by some value:
amount += amountToIncrease;
if (amount > full) amount = 0; // restart
}
draw();
// Every second we'll fill more;
setInterval(draw, 1000);
http://jsfiddle.net/simonsarris/pby9r/
This is a little more dynamic, object-oriented version, so you can configure the options as the circle radius, border width, colors, duration and step of animation, you can also animate the circle to a certain percentage. It was quite fun to write this.
<canvas id="Circle" width="300" height="300"></canvas>
<script>
function Animation( opt ) {
var context = opt.canvas.getContext("2d");
var handle = 0;
var current = 0;
var percent = 0;
this.start = function( percentage ) {
percent = percentage;
// start the interval
handle = setInterval( draw, opt.interval );
}
// fill the background color
context.fillStyle = opt.backcolor;
context.fillRect( 0, 0, opt.width, opt.height );
// draw a circle
context.arc( opt.width / 2, opt.height / 2, opt.radius, 0, 2 * Math.PI, false );
context.lineWidth = opt.linewidth;
context.strokeStyle = opt.circlecolor;
context.stroke();
function draw() {
// make a circular clipping region
context.beginPath();
context.arc( opt.width / 2, opt.height / 2, opt.radius-(opt.linewidth/2), 0, 2 * Math.PI, false );
context.clip();
// draw the current rectangle
var height = ((100-current)*opt.radius*2)/100 + (opt.height-(opt.radius*2))/2;
context.fillStyle = opt.fillcolor;
context.fillRect( 0, height, opt.width, opt.radius*2 );
// clear the interval when the animation is over
if ( current < percent ) current+=opt.step;
else clearInterval(handle);
}
}
// create the new object, add options, and start the animation with desired percentage
var canvas = document.getElementById("Circle");
new Animation({
'canvas': canvas,
'width': canvas.width,
'height': canvas.height,
'radius': 100,
'linewidth': 10,
'interval': 20,
'step': 1,
'backcolor': '#666',
'circlecolor': '#fff',
'fillcolor': '#339999'
}).start( 70 );
</script>