So at the moment i'm working on a small project that renders an isometric tile space using diamonds that are at a height : width ratio of 1:2.
I plan on making a "shield" effect above them by basically putting more and more blue closer to the edges with the inner circle being transparent to give the illusion of a semi-transparent sphere being above it.
To do this i am using radial gradient.
Currently my canvas when rendered looks like this:
And my code looks like this:
var gradient = ctx.createRadialGradient(450, tileheight * 5 / 2, 100, 450, tileheight * 5 / 2, 200);
gradient.addColorStop(0, "rgba(0, 0, 55, 0.5)");
gradient.addColorStop(1, "rgba(10, 10, 55, 0.8)");
drawdiamond(gradient, 450, 0, tilewidth * 5, tileheight * 5);
Where drawdiamond is a function that draws the tiles, this function will draw a diamond the size of the map with the gradient specified by the variable "gradient".
And tilewidth and tileheight are the width and height at the longest parts of each tile, they are 100 and 50 respectively.
If you look at the image, there is a problem, since i'm drawing my tiles to be wider than they are taller, i need to draw an oval where the height is half the radius of the width in order for the effect to be convincing.
There is ctx.setPattern where i can make an image that has the gradient i want, but if i want to make the map bigger during run time, i would need to create multiple images for each size, which is not ideal.
Is there a way to transform the image gradient so it draws an oval instead of a circle?
(Sorry if this has already been posted).
Matt posted an article that suggested making a shape that is larger than it should be height wise, they use ctx.transform to set the height scaling to 0.5, which will scale the gradient with it as it's part of what's drawn.
I played around with it and came up with this, thanks for the help!
var gradient = ctx.createRadialGradient(450, tilewidth * mapsize / 2, 100, 450, tilewidth * mapsize / 2, 200);
gradient.addColorStop(0, "rgba(0, 0, 55, 0.5)");
gradient.addColorStop(1, "rgba(10, 10, 55, 0.8)");
ctx.setTransform(1, 0, 0, 0.5, 0, 0);
drawdiamond(gradient, 450, 0, tilewidth * mapsize, tilewidth * mapsize);
ctx.setTransform(1, 0, 0, 1, 0, 0);
This function draws the diamond with the shield effect at double it's intended height, then scales it down when it is drawn, thus causing a scaled gradient.
Map size is the maps length in tiles on both length and height sides.
This will work for any size map.
Related
Suppose I draw two overlapping translucent shapes in a canvas:
context.fillStyle = "rgba(0, 0, 0, 0.25)";
context.globalCompositeOperation = "copy";
context.fillRect(50, 50, 200, 200);
context.globalCompositeOperation = "??????";
context.fillRect(150, 150, 200, 200);
After this, I want the RGB values of each pixel to be (0, 0, 0) and for the alpha to be the maximum of the alpha channels of the two shapes. In other words, what I should get is that if a pixel is in either of the two squares it has alpha 0.25, otherwise it's blank. So the entire drawn region should be a uniform color.
I can't find any blending operation that does this. If I use things like "lighter" or "darker", I get a darker region where the two squares overlap.
I've been experimenting with the <canvas> recently, and I noticed a strange behaviour when stroking rectangles near the origin (0, 0) of the canvas.
// canvas context
var ctx = document.querySelector('#screen').getContext('2d');
// draw a rectangle
ctx.fillStyle = 'orange';
ctx.fillRect(0, 0, 100, 100);
// stroke a border for the rectangle
ctx.lineWidth = 20;
ctx.strokeRect(0, 0, 100, 100);
<canvas id="screen"></canvas>
What went wrong?
In the example above, the rectangle itself was drawn at (0, 0) as intended, but its border (the stroked rectangle) seems to be drawn at an offset.
Generally, when stroking a rectangle at a position away from the origin, this effect is omitted —
Meaning that the stroked rectangles aren't being drawn starting at the position specified, but at an offset, I suppose.
Why is that?
The stroke is centered around the coordinates that your primitve is defined at. In the case of your rectangle with stroke width of 20, drawing this at the top left of the canvas will cause half of the strokes width to be drawn outside of the canvas boundary.
Adjusting the coordinates of strokeRect() to 10,10,.. causes the rectangle to be offset from the canvas origin, meaning that the full stroke of 20 pixels will be visible from the top-left of the canvas:
ctx.lineWidth = 20;
ctx.strokeRect(10, 10, 100, 100);
Consider the following adjustments, made to ensure the stroke is fully visible around the drawn rectangle:
var canvas = document.querySelector('#screen');
// Set the width and height to specify dimensions of canvas (in pixels)
// Choosing a 100x100 square matches the strokeRect() drawn below and
// therefore achieves the appearance of a symmetric stroke
canvas.width = 100;
canvas.height = 100;
// canvas context
var ctx = canvas.getContext('2d');
// draw a rectangle
ctx.fillStyle = 'orange';
ctx.fillRect(10, 10, 90, 90);
// stroke a border for the rectangle
ctx.lineWidth = 20;
var halfStroke = ctx.lineWidth * 0.5;
ctx.strokeRect(halfStroke, halfStroke, 100 - (halfStroke * 2), 100 - (halfStroke * 2));
<canvas id="screen"></canvas>
Update
Here is a visualisation of the stroke in relation to the line/rectangle edge provided by Ibrahim Mahrir:
I'm trying to draw a circle with cut off sides looking somewhat like this:
My first approach was to just draw a stroke-circle and do clearRect on the sides - but I want to render many of these adjacent to each other and I can't afford to clear what's already been drawn on the canvas.
var size = 100;
c.save();
c.strokeStyle = '#ff0000';
c.lineWidth = 50;
c.beginPath();
c.arc(0, 0, size - c.lineWidth / 2, 0, Math.PI * 2);
c.closePath();
c.stroke();
// clear rects on each side to get this effect
c.restore();
Is there a way to limit the arc to not draw further or is there a way to clear on just my little shape somehow and later add it to the main canvas?
I'm not keen on the idea of having multiple canvas elements on top of each other.
Just add a clip mask to it:
DEMO
c.save();
/// define clip
c.beginPath();
c.rect(120, 120, 160, 160);
c.clip();
/// next drawn will be clipped
c.beginPath();
c.arc(200, 200, size - c.lineWidth / 2, 0, Math.PI * 2);
c.closePath();
c.stroke();
// clear rects on each side to get this effect
/// and remove clipping mask
c.restore();
The clip() method uses the current defined path to clip the next drawn graphics.
In attempting to get a drawing of a canvas and redrawing it onto the same canvas later is giving unexpected behavior. See Jsfiddle for example.
The logic is fairly straight forward:
Given an image on the canvas, and translation to the center (I'm using a rectangle to demonstrate).
Clip out a section of the image. I'm simply copying the entire "image" in the example.
Possibly do manipulation.
Paste back to the same canvas after clearing it. The point being to clip a section of the original image at a given point with a width / height, and paste it later with modifications.
var c=document.getElementById("cvs1"),
ctx = c.getContext("2d"),
bufferCvs = document.createElement('canvas'),
bufferCtx = bufferCvs.getContext('2d');
bufferCvs.width = c.width;
bufferCvs.height = c.height;
ctx.translate( c.width/2, c.height/2 );
ctx.fillStyle="#FF0000";
ctx.fillRect( -75, -50 ,150,100);
//Image data experiment
//Set the buffer width / height same size as rectangle
bufferCvs.width = 150;
bufferCvs.height = 100;
//Draw the "image" from the first context to the buffer by clipping the first, and pasting to 0,0 width the same "image" dimensions.
bufferCtx.drawImage( c, -75, -50, 150, 100, 0, 0, 150, 100 );
//clear out old canvas drawing
ctx.save()
ctx.setTransform(1,0,0,1,0,0,1,0,0);
ctx.clearRect(0, 0, c.width, c.height);
ctx.restore();
ctx.drawImage( bufferCvs, -75, -50, 150, 100 );
Since I'm keeping the exact same coordinates / dimensions, the expected output would be what was originally on canvas to begin with. However, only part of the upper left corner is drawn (see fiddle). I'm using drawImage for efficiency reasons, but I've used get/putImageData with the same results. Both width and height are defined as mentioned to fix other strange behaviors.
How would you go about making sure everything stored in the buffer canvas is drawn, instead of just the top corner?
Edit:
To help with my question and what behavior I believe is going on I'll post some screens.
Step 1:
Translate context 1 to the center, and draw a rectangle to represent an image.
ctx.translate( c.width/2, c.height/2 );
ctx.fillStyle="#FF0000";
ctx.fillRect( -75, -50 ,150,100);
Step 2:
Use drawImage to "clip" from the -75, -50 point and cut out just the rectangle using the width 150, 100. This should be drawn onto the canvas buffer, but it is not
bufferCvs.width = 150;
bufferCvs.height = 100;
//Draw the "image" from the first context to the buffer by clipping the first at -75, -50 (the start of the image), and pasting to 0,0 width the same "image" dimensions.
bufferCtx.drawImage( c, -75, -50, 150, 100, 0, 0, 150, 100 );
I would expect the buffer canvas to look like this (It is not):
However, if I change the drawImage to
bufferCtx.drawImage( c, 0, 0, 150, 100, 0, 0, 150, 100 );
I get an expected amount of white space on the buffer (and the last drawImage back to the context without issue)
Step 3: Clear out the old "image" from the first context. This shouldn't change the translation since I restore the context state after performing the clear. (This works as expected)
ctx.save()
ctx.setTransform(1,0,0,1,0,0,1,0,0);
ctx.clearRect(0, 0, c.width, c.height);
ctx.restore();
Step 4: Simply take what is in the buffer canvas and draw it onto the original context where it started to return to this:
The idea is to more "clip" a region of the original, clear the old image, and paste the new clipped region centered back into the original context. I have looked at MDN's example, but the clipping portion of drawImage isn't performing as I expect.
The main issue is that the first canvas drawn over the bufferCvs is not drawn on the place where you expect. The easiest way to prove this is by adding bufferCvs to the DOM tree: http://jsfiddle.net/CdWn6/4/
I guess this is what you're looking for: http://jsfiddle.net/CdWn6/6/
var c=document.getElementById("cvs1"),
c2 = document.getElementById("cvs2"),
bufferCvs = document.createElement('canvas'),
bufferCtx = bufferCvs.getContext('2d'),
ctx = c.getContext("2d"),
ctx2 = c2.getContext("2d");
bufferCvs.width = c.width;
bufferCvs.height = c.height;
ctx.translate( c.width/2, c.height/2 );
ctx.fillStyle="#FF0000";
ctx2.translate( c.width/2, c.height/2 );
ctx2.fillStyle="#FF0000";
bufferCtx.translate( c.width/2, c.height/2 );
ctx.fillRect( -75, -50 ,150,100);
ctx2.fillRect( -75, -50 ,150,100);
//Image data experiment
bufferCtx.drawImage( c, -135, -110, 270, 220);
ctx.save()
ctx.setTransform(1,0,0,1,0,0,1,0,0);
ctx.clearRect(0, 0, c.width, c.height);
ctx.restore();
//Draw background to demonstrate coordinates still work
ctx.fillStyle="#00FF00";
//ctx.fillRect( -75, -50 ,150,100);
ctx.drawImage( bufferCvs, -75, -50, 150, 100 );
I'm attempting to make a program that takes the information gathered from some calculations and plots it on a canvas graph. I need to scale the graph, however, so that it can accommodate larger numbers. But every time I put ctx.scale(); the whole canvas blanks out! I thought I could stop this by scaling the canvas first, but nothing is drawn on the canvas after I scale it.
Here's the coding for my canvas:
var c=document.getElementById("graph_");
var ctx=c.getContext("2d");
graph_.style.backgroundColor="white";
var z0=Math.max(Math.abs(a),Math.abs(b));
var z=Math.round(z0);
var z1=Math.round(z);
var z2=z*2
// alert(z1);
// alert(z2);
ctx.scale(3200/z,3200/z)
var xnew=360/2+360/2*a
var ynew=360/2-360/2*b
alert(xnew);
alert(ynew);
ctx.font = '10px Calibri';
ctx.fillText("( 0 , 0 )", 125, 85);
ctx.fillText(z1, 210, 87);
ctx.fillText(z2, 270, 87);
ctx.fillText(z1*-1, 75, 87);
ctx.fillText(z2*-1, 0, 87);
ctx.fillText(z1, 120, 43.5);
ctx.fillText(z2, 120, 10);
ctx.fillText(z1*-1, 120, 120);
ctx.fillText(z2*-1, 120, 145);
ctx.lineWidth = 1;
ctx.beginPath()
ctx.moveTo(150, 0);
ctx.lineTo(150, 400);
ctx.closePath();
ctx.lineWidth = .2;
ctx.moveTo(0, 75);
ctx.lineTo(400, 75);
ctx.strokeStyle = "#8B8682";
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.moveTo(xnew, 180);
ctx.lineTo(180, ynew);
ctx.strokeStyle = "red";
ctx.stroke();
Actually, the stuff is being drawn to the canvas, you just can't see it because you're both too far zoomed in and still in the upper left corner of the graph since the default origin points for drawing are in the top left as 0,0.
So if you want to zoom in that far (even though you probably want to zoom out to show bigger numbers, i.e. larger drawings on the graph) you need to translate the canvas origin point to your new origin point (the top left of what you want to see) before you scale the context.
You can use the translate method like
ctx.translate(newX,newY);
But before you do you're going to what to save the context's state so you can revert back to it.
Say you wanted to zoom in on the center of the graph you would translate to the point that is:
ctx.translate((-c.width /2 * scale) + offsetX,(-c.height / 2 * scale) + offsetY);
where the offsetX is the canvas width / 2 and offsetY is the canvas height / 2 and the scale is by the amount that you're scaling in you ctx.scale call.
What is the value of 3200/z, exactly?
I'm guessing that you are scaling your canvas by an enormous amount, so much so that the only thing visible on your screen would be the first few pixels of the canvas. Since you don't draw anything in the top-left 5 pixels of the screen, you don't see anything.