Gradient fill relative to shape position, not canvas position? - javascript

As I understand it, gradient fills must be specified relative to the canvas element itself (i.e. 0, 0) rather than relative to the shape you're actually filling.
Question: am I right in this assertion, and is there a suggested way around it?
For example (JSFiddle here):
ctx.beginPath();
ctx.rect(40, 50, 100, 70);
var grd = ctx.createLinearGradient(0, 50, 0, 120);
grd.addColorStop(0, "red");
grd.addColorStop(1, "blue");
ctx.fillStyle = grd;
ctx.fill();
There, I make a rectangle. I expected that, to fill it with a gradient starting from the top left of the shape, I would pass 0, 0 as the first two params. It seems I must pass instead the X/Y coordinates of the rectangle relative to the canvas.
This becomes an issue if, say, you have an arc built with a quadratic curve, since, without being a Maths genius, you don't know the top position of the curve - only the control point.

If you don't know the bounds of the shapes that you're drawing, you're going to have a bad time.
If you're using gradients, especially re-using gradients, it's best to always make your gradients and shapes from the origin and translate them to the locations they are going to be.
Setting up that sort of system will make it so that defining gradients is more or less relative to the size of your objects, but you'll still have to do the bounds calculations yourself.
Here's an example of translating a gradient and shape to make it "relative" to the canvas:
http://jsfiddle.net/simonsarris/RFgcs/

Related

Is there an easy way in p5.js to draw a transparent shape without having the outline being drawn onto that shape?

I am trying to draw an opaque shape on top of a colorful background in p5.js. So I give both the fill color and the stroke color an alpha value. The problem is that the outline of the shape is partly drawn onto the shape. Because of the opaque stroke, this makes it look like there is two outlines with different colors. Here is an example:
function setup() {
createCanvas(60, 60);
}
function draw() {
background(255);
fill(127,127);
stroke(50,127);
strokeWeight(5);
rect(10,10,40,40);
}
What I get is this square which appears to have an inner dark gray and an outer light gray bounding box.
Is there an easy way to prevent this from happening? I know I could draw the shape "twice" (first the fill and then the outline a little bit further out), but
I am trying to draw a rather complicated polygon, so I would have to adjust all the vertex coordinates of the outer shape which would be pretty annoying to work out.
I have already tried the different blendModes, but they didn't seem to be able to solve my problem.
The reason this is happening is because you have a stroke weight of 5, which is drawn from the edge of the shape, in both directions. So some of the stroke is on the inside of the shape, and some of the stroke is on the outside.
I don't know of a way to change this setting. I tried playing with strokeCap and strokeJoin but nothing solved the problem.
One option that occurs to me is drawing a scaled-down version of the shape with a stroke weight of 1. That would look something like this:
let shapeGraphics;
function setup() {
createCanvas(260, 260);
// draw a scaled-down version of the shape
shapeGraphics = createGraphics(8, 8);
shapeGraphics.fill(127, 127);
shapeGraphics.stroke(50, 127);
shapeGraphics.rect(0, 0, 8, 8);
}
function draw() {
background(255);
noSmooth();
// draw a scaled-up version of the graphics
image(shapeGraphics, 10, 10, 40, 40);
}
If that doesn't work, then the only other option I can think of is to calculate the outer edges yourself and draw the shape twice, like you mentioned.

Can I define a rectangle in Canvas2D before drawing it?

Basically, I want to have a large rectangle and cut smaller rectangles out of it. I can draw the rectangle, then do clearRect(x, y, w, h) but it removes stuff I've drawn on previous to even creating the rectangle.
So basically, can I draw a rectangle over an image and then cut the rectangle without affecting the image underneath it? I have a small idea of using ctx.save and restore, but I can't quite get it to work - and I'm not 100% sure that's the right answer.
Any help would be greatly appreciated!
Thanks,
Cam
An alternative might be to use paths:
Call context.beginPath()
Define the outer rect with moveTo and lineTo and closePath, going clockwise
Cut out the holes the same way but, going counter-clockwise
Call context.fill() to fill the rectangle without the holes.
This only works if you know all the holes from the start, though. This answer has more details for this approach:
Polygon with a hole in the middle with HTML5's canvas
Thanks to markE and Stefan for giving a solution. However, after hours of searching I found my own answer based on this answer. I have no idea why it works, but check out what code I used below:
ctx.fillStyle = "grey";
ctx.beginPath();
ctx.rect(100, 100, 50, 50);
ctx.rect(400, 0, -400, 400);
ctx.fill();
For some reason, if you declare what shape you want to cut out first, and then draw the overlaying shape BACKWARDS (?!) over your area, it cuts out the shape. WTF Javascript? Anyways, it works :)

I can't use negative numbers for height and width in a box, fillRect, drawImage, etc

I am making a 3d game and to make a cube, an image needed to rotate more than to it's 0 point:
the last image is like seeing the back of the image as it was transparent. It has rotated round......
I tried this, I think it should work, or didn't the javascript developers think of it? :
var ctx = document.getElementById("canvas").getContext("2d");
ctx.beginPath();
ctx.fillStyle = "red";
ctx.fillRect(100, 100, -100, - 100);
Here is my 3d thingy for those who are interested : Just for fun, try to use it also with sphere, in your notepad
Instead of using negative widths, you can use negative scale factors, e.g. if you use ctx.scale(-1, 1) then all of your coordinates will draw from right to left instead of left to right.
See https://jsfiddle.net/alnitak/eqyqmdk3/ for a demo.

Canvas 2D maze torch effect

I am working on a school project that includes these conditions:
Make maze with using only JS, HTML5 and CSS.
Make a torch effect around the character. You cannot light through walls.
I started making this game with the use of canvas.
I have succeeded to make a torch effect around the character as shown here:
http://people.inf.elte.hu/tunyooo/web2/HTML5-Maze.html
However, I cannot make it NOT to light through walls.
I am fairly sure I should do something like this:
Start a loop in all directions from the current position of the character up until it reaches the view distance OR if the context.getImageData() returns [0,0,0,255]. This way, I could get the character's distance from northern, eastern, western and southern walls.
Then, I could light the maze around the character with a (viewdistance-DistanceFrom*Wall) rectangle.
Unfortunately though, after 15 hours of thinking about this I am running out of ideas how to make this work.
Any tips are appreciated.
A simpler way of doing this is (ps: I get a "forbidden" error on the link provided so i cannot see what you did):
Have a matte version of the maze, a transparent and white image where white represent allowed drawing areas. This matte image should match the maze image in size and placement.
Create an off-screen canvas the size of the torch image
When you need to draw the torch first draw the matte image into the off-screen canvas. Draw it offset so the correct part of the matte is drawn. For example: if the torch will be drawn at position 100, 100 on the maze then draw the matte into the off-screen canvas at -100,-100 - or simply create the canvas the same size as the maze and draw in the matte at 0,0 and the torch at the relative position. More memory is used but simpler to maintain.
Change composite mode to source-in and then draw the torch. Change composite mode back to copy for the next draw.
Now your torch is clipped to fit within the wall. Now simply draw the off-screen canvas to your main canvas instead of the torch.
Note: it's important that the torch is made such as it cannot reach the other side of the wall (diameter size) or it will instead shine "under" the maze walls - this can be solved other ways though by using matte for different zones which is chosen depending on player position (not shown here).
To move in the demo below just move the mouse over the canvas area.
Live demo
function mousemoved(e) {
var rect = canvas.getBoundingClientRect(), // adjust mouse pos.:
x = e.clientX - rect.left - iTorch.width * 0.5, // center of torch
y = e.clientY - rect.top - iTorch.height * 0.5;
octx.drawImage(iMatte, 0, 0); // draw matte to off-screen
octx.globalCompositeOperation = 'source-in'; // change comp mode
octx.drawImage(iTorch, x, y); // clip torch
octx.globalCompositeOperation = 'copy'; // change comp mode for next
ctx.drawImage(iMaze, 0, 0); // redraw maze
ctx.drawImage(ocanvas, 0, 0); // draw clipped torch on top
}
In the demo the torch is of more or less random size, a bit too big in fact - something I made quick and dirty. But try to move within the maze path to see it being clipped. The off-screen canvas is added on the size of the main canvas to show what goes on.
An added bonus is that you could use the same matte for hit-testing.
Make your maze hallways into clipping paths.
Your torch effects will be contained within the clipping paths.
[ Addition to answer based on questioner's comments ]
To create a clipping path from your existing maze image:
Open up your maze image in a Paint program. The mouse cursors X/Y position are usually displayed as you move over the maze image.
Record the top-left and bottom-right of each maze hallway in an array.
var hallways=[];
hallways.push({left:100, y:50, right: 150, bottom: 65}); // for each hallway
Listen for mouse events and determine which hallway the mouse is in.
// hallwayIndex is the index of the hallway the mouse is inside
var hallwayIndex=-1;
// x=mouse's x coordinate, y=mouse's y coordinate
for(var i=0;i<hallways;i++){
var hall=hallways[i];
if(x>=hall.left &&
x<=hall.right &&
y>=hall.top &&
y<=hall.bottom)
{ hallwayIndex=i; }
}
Redraw the maze on the canvas
Create a clipping path for the current hallway:
var width=hall.right-hall.left;
var height=hall.bottom-hall.top;
ctx.beginPath();
ctx.Rect(hall.left,hall.top,width,height);
ctx.clip();
Draw the player+torch into the hallway (the torch will not glow thru the walls).
There is a brilliant article on this topic: http://www.redblobgames.com/articles/visibility/
Doing it accurately like that, however, is a lot of work. If you want to go with a quick and dirty solution, I would suggest the following. Build the world from large blocks (think retro pixels). This makes collision detection simpler too. Now you can consider all points within the torch radius. Walk in a straight line from the character to the point. If you reach the point without hitting a wall, make it bright.
(You could do the same with 1-pixel blocks too, but you might hit performance issues.)

clear pixels under a shape in HTML canvas

I am using an HTML canvas and javascript and I need to clear all of the pixels underneath a shape created by closing a path (for example, I am using flot, and I want to make rounded corners, and to do this, I first need to remove the square corners by drawing a curve on top of the corner to remove the desired pixels).
Right now, I am doing this by just filling the shape with the same color as the background, which can imitate what I want to do, but, it is not ideal as it makes it impossible to place the chart on top of non-solid backgrounds without seeing the square corners. I know that there is a clearRect method that would do what I want to do, but with only rectangles, I need to do it with any closed shape. Is it possible, and if so, how would I do it?
brainjam's code was heading in the right direction, but didn't fully solve the problem. Here's the solution:
context.save();
context.globalCompositeOperation = 'copy';
context.fillStyle = 'rgba(0,0,0,0)';
//draw shape to cover up stuff underneath
context.fill();
context.restore();
Here's an example of a function that will clear a circle from a canvas:
var clearCircle = function(x, y, radius)
{
context.save();
context.globalCompositeOperation = 'destination-out';
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, false);
context.fill();
context.restore();
};
I think what you want is a clipping region, defined by the clip() function. The latter takes a bunch of paths. Here's an example.
This is a little different from what you are specifically asking (which is to remove pixels after drawing them), but actually not drawing the pixels in the first place is probably better, if I understand your requirements correctly.
Edit: I now think I understand that what you want to do is clear pixels to transparent black. To do that, after having defined your paths, do something like this:
context.fillStyle = 'rgba(0,0,0,0)';
context.fill();
The first statement sets the fill color to transparent black.
Use globalCompositeOperation = 'destination-out' instead of 'copy', it will erase all pixels of the shape in the canvas.
See all kinds of composition here
very usefull !

Categories

Resources