Javascript Canvas Drawing moves out of canvas after translate() - javascript

I am having a problem with randomising rectangle locations on the screen. I have a 5 x 5 grid set up with in html and they are all formatted properly. I am then drawing bars into this grid which also works fine. The problem is that when I want to randomly jitter these objects they sometimes move off the canvas (to the left, or to the top only) apparently. I am trying to reposition the canvas via jQuery with no success.
Also, I have the feeling my canvas sizes are wrong but if I set the sizes to 100% in the CSS section, the bars are suddenly very small and I have no idea why.
Here is the interesting piece of the code. the problem is the "jitter" part
function drawStimulus(size, color, orientation, location){
// size for each box, refers to the first box in the first row
// box size should be around 40 on a 22inch screen in this jsfiddle
var box_size = $("#testbox").height();
var stimulus_size = box_size * size; // size is either 1 (large) or 2/3
var size_diff = box_size-stimulus_size;
var c = document.getElementById(location);
c.width = box_size;
c.height = box_size;
// if a perfect grid is not wanted, jitter bars randomly
// by a maximum of 1/3 of the box size in any direction
if(jitter){
var hjitter = rand(-box_size/3, box_size/3);
var vjitter = rand(-box_size/3, box_size/3);
c.style.left = String(c.style.left+hjitter)+"px";
c.style.top = String(c.style.top+vjitter)+"px";
}
var ctx = c.getContext("2d");
// rotate around center
ctx.translate(box_size/2, box_size/2);
ctx.rotate(orientation*Math.PI / 180);
ctx.translate(-box_size/2, -box_size/2);
// draw bars
ctx.beginPath();
ctx.strokeStyle = color;
ctx.moveTo(c.offsetLeft+size_diff/2,c.offsetTop+box_size/2);
ctx.lineTo(c.offsetLeft+stimulus_size+size_diff/2,c.offsetTop+box_size/2);
ctx.lineWidth = size*10;
ctx.closePath();
ctx.stroke();
}
Here is also a jsfiddle with the full code: http://jsfiddle.net/msauter/pdrwwm7x/

Related

Javascript Canvas issue: Why do my points on the canvas not correspond properly to the height of the graph?

I'm trying to make a line graph using the canvas that looks like a typical line graph and uses typical Cartesian coordinates like we learned in algebra;
starts with 0,0 at the bottom left, and the position x-axis is to be determined by the number of items to chart.
However, the position of the points doesn't match the input (although the shape of the graph is correct, indicating I'm doing something right). What am I doing wrong?
I've rewritten and tweaked the formula for converting numerous times
function newLineGraph(parent, width, height, dataArray) {
//this makes the element using my own code, no observable error here
var canvas = newCanvas(parent, width, height);
var canvasContext = canvas.getContext("2d");
var spaceBetweenEntries = width / dataArray.length;
var largestNumber = findHighestNumber(dataArray);
canvasContext.beginPath();
canvasContext.moveTo(0, 0);
var n = 0;
while (dataArray[n]) {
var x = spaceBetweenEntries * n;
var y = height - dataArray[n];
console.log("x,y", x, y);
canvasContext.lineTo(x, y);
n++;
}
canvasContext.stroke();
return canvas;
}
edit: fixed the image so you can see the canvas size
The resulting graph is much smaller than the intended graph; for example
newLineGraph("body",55,45,[1,40,10]);
produces a graph with a small ^ shape in the corner, rather than properly starting at the bottom. However, the console logs show " 0 44" "18.333333333333332 5","36.666666666666664 35" which I believe should produce a graph that fits the whole chart nicely.
The first lineTo will always have x as 0 so I assume the first line isn't drawing like you intended. It is more like a |/\ shape instead of \/\.
Set x like this:
var x = spaceBetweenEntries * (n + 1);
Edit
As you can see in this fiddle your chart renders at the right points with the coordinates you posted. I implemented the newCanvas function like I expect it to behave. So are we missing some other code that modifies the canvas width and height?
function newCanvas(parent, width, height) {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
document.querySelector(parent).appendChild(canvas);
return canvas;
}
The problem was using style.width and style.height to modify the canvas height, instead of canvas.height and canvas.width

coding half a million circles in a pattern

I´ve been trying to make a desktop app (javascript, canvas) and draw 413.280 clickable circles in a certain pattern, but I can´t really figure out how to do it. I´m not convinced canvas is the best solution but I dont know how to solve this and get an app with a reasonable performance.
Here´s the layout I´m trying to get:
circle layout
I want 2 rows of circles within each line. the division in the middle is to be left empty.
Every left row has to be 588 circles.
Every right row has to be 560 circles
There are 180 lines on each side which means there's (588*2*180)= 211680 circles on the left side.
There's (560*2*180)=201600 circles on the right side.
can anyone point me in the right direction, maybe have a clue how I can solve this in the most efficient way possible? Thanks in advance.
EDIT: here's the JSFiddle I've got so far jsfiddle.net/cmxLoqej/2/
JavaScript
window.onload = draw;
function draw() {
var canvas = document.getElementById('canvas');
var c = canvas.getContext('2d');
var ycoordinate = 20;
//draw the line 180 times
for (var x = 1; x <= 180; x++) {
// draw the left side
for (var i = 1; i <= 1; i++){
c.strokeStyle = 'black';
c.moveTo(0,ycoordinate);
c.lineTo(6468,ycoordinate);
c.stroke();
ycoordinate = ycoordinate + 40;
}
}
var ycoordinate = 20;
//draw right side
for (var x = 1; x <= 180; x++) {
for (var j = 1; j <= 1; j++){
c.strokeStyle = 'black';
c.moveTo(6776,ycoordinate);
c.lineTo(canvas.width,ycoordinate);
c.stroke();
ycoordinate = ycoordinate + 40;
}
}
}
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var canvasPattern = document.createElement("canvas");
canvasPattern.width=11;
canvasPattern.height=20;
var contextPattern = canvasPattern.getContext("2d");
contextPattern.beginPath();
contextPattern.arc(5, 10, 5, 0, 2 * Math.PI, false);
contextPattern.strokeStyle = '#003300';
contextPattern.stroke();
var pattern = context.createPattern(canvasPattern,"repeat");
context.fillStyle = pattern;
context.fillRect(0, 20, 6468, 7160);
context.fill();
var canvas2 = document.getElementById('canvas');
var context2 = canvas.getContext('2d');
var canvasPattern2 = document.createElement("canvas");
canvasPattern2.width=11;
canvasPattern2.height=20;
var contextPattern2 = canvasPattern.getContext("2d");
contextPattern2.beginPath();
contextPattern2.arc(5, 10, 5, 0, 2 * Math.PI, false);
contextPattern2.strokeStyle = '#003300';
contextPattern2.stroke();
var pattern2 = context2.createPattern(canvasPattern2,"repeat");
context2.fillStyle = pattern;
context2.fillRect(6776, 20, 6160, 7160);
context2.fill();
HTML
<!DOCTYPE html>
<html>
<body>
<canvas {
id="canvas";
width= "12936" ;
height ="7400";
style= "border: 1px solid black;";
padding: 0;
margin: auto;
display: block;
}>
</canvas>
</body>
</html>
Use fill patterns of circles to create rectangular canvas images of
a single row of the left hand side
a single row of the right hand side
a combined row of each side
a single canvas of 180 rows
Use temporary CANVAS objects along the way as necessary to use the context2D.createPattern method. You should not need to add them to the DOM just to manipulate pixels.
Modify the algorithm if needed as you learn. Happy coding!
Update (edit)
Running the code added to the question shows all circles being evenly spaced horizontally and vertically.
A simpler way of drawing the canvas may be to fill two rectangles that exactly cover the left and right areas of the canvas with the circle pattern, and draw the grid lines on the canvas afterwards instead of before.
Finding the circle clicked
A click event listener on the canvas is passed a mouse event object.
The classical way to determine which circle was clicked was to first perform arithmetic on the screenX and screenY event properties for screen position, window.scrollX and window.scrollY for document scroll amounts, and the position of the canvas within the document, to find where the click occured in the canvas.
Although not yet fully standardized, offsetX and offsetY properties of the mouse event object provide the result directly. The MDN reference shows fairly good cross browser support.
Then knowledge of canvas layout can be used to determine which rectangular circle pattern was clicked, and with a bit of algebra if the click is inside the circle.

Rotate a full size canvas image from a centerpoint without moving other canvas images

the example of the code below can be viewed here - http://dev.touch-akl.com/celebtrations/
What I have been trying to do is draw 2 images onto the canvas (glow and then flare. links for these images are below)
http://dev.touch-akl.com/celebtrations/wp-content/themes/beanstalk/img/flare.jpg
http://dev.touch-akl.com/celebtrations/wp-content/themes/beanstalk/img/blue-background.jpg
The goal is for the 'blue-background' image to sit on the canvas at the height and width of the container, and for the 'flare' image to be drawn ontop of this image with a blending mode and rotated with an animation to create a kind of twinkle effect.
My problem is that because the images I am using are rectangular when the 'flare' rotates at certain points you can see the edges of the layer underneath...
What I tried to do was find the diagonal width of the container using trigonometry and draw the 'flare' image at that width so that it always covered the whole canvas but alas you can still see the background layer at some points (but much less than before).
I need a way for the flare image to always cover the whole canvas, can anyone point me in the right direction please?
var banner = $('#banner'),
flare = document.getElementById('flare'),
glow = document.getElementById('glow'),
canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
blendMode = "multiply";
$window.load(function(){
_canvasWidth = banner.outerWidth(),
_canvasHeight = banner.outerHeight();
canvas.width = _canvasWidth;
canvas.height = _canvasHeight;
var _flareSum = (_canvasWidth * _canvasWidth) + (_canvasHeight * _canvasHeight);
_flareWidth = Math.sqrt(_flareSum);
_angle = 0;
setInterval(function() {
_angle = _angle +0.25;
// draw the bg without a blend mode
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(glow, 0, 0, _canvasWidth, _canvasHeight);
ctx.save();
// clear the canvas
// ctx.clearRect(0, 0, _canvasWidth, _canvasHeight);
ctx.translate( _canvasWidth/2, _canvasHeight); // move to center point
ctx.globalCompositeOperation = blendMode;
ctx.rotate(Math.PI / 180 * (_angle)); // 1/2 a degree
ctx.drawImage(flare, -_flareWidth/2, -_flareWidth/2, _flareWidth, _flareWidth); // redraw ia=mages
ctx.restore();
//console.log(_angle)
}, 1);
If I understand correctly, you need the shortest part of the flare to still cover the canvas when the flare is rotated at any angle.
Since you're only showing half the flare at any time, the shortest part of the flare is the distance from the flare center to the top of the flare:
var flareMinHeight = flare.height/2;
The longest length the flare must cover is from the flare rotation point to the top-left of the canvas.
var dx=rotationPointX;
var dy=rotationPointY;
var requiredLength=Math.sqrt(dx*dx+dy*dy);
So you will need to scale the flare to be at least the length computed above:
var minScale = requiredLength / flareMinHeight;

why is my strokeStyle transparent?

I am drawing onto an HTML5 canvas with stroke() and regardless of how or when I set globalAlpha, the stroke is being drawn with some measure of transparency. I'd like for the stroke to be completely opaque (globalAlpha=1). Is there somewhere else where the alpha is being set?
In this jsfiddle, I am drawing a grid of solid black lines onto a canvas. For me, the result shows dots at the intersections, confirming that the lines are partially transparent. Here's the gist of it:
context.globalAlpha=1;
context.strokeStyle="#000";
context.beginPath();
/* draw the grid */
context.stroke();
context.closePath;
The especially weird thing (to me) is that this problem was not occurring in my code before my last computer restart, so I'm guessing there was something hanging around in the cache that was keeping the alpha at my desired level.
I'm obviously missing something here... thanks for any help you can provide.
Real answer :
Each point in a canvas has its center in its (+0.5, +0.5) coordinate.
So to avoid artifacts, start by translating the context by (0.5, 0.5) ,
then round the coordinates.
css scaling creates artifact, deal only with canvas width and height, unless
you want to deal with hidpi devices with webGL, or render at a lower resolution
with both webGL and context2D.
-> in your case, your setup code would be (with NO css width/height set ) :
( http://jsfiddle.net/gamealchemist/x9bTX/8/ )
// parameters
var canvasHorizontalRatio = 0.9;
var canvasHeight = 300;
var hCenterCanvas = true;
// setup
var canvasWidth = Math.floor(window.innerWidth * canvasHorizontalRatio);
var cnv = document.getElementById("myCanvas");
cnv.width = canvasWidth;
cnv.height = canvasHeight;
if (hCenterCanvas)
cnv.style['margin-left'] = Math.floor((window.innerWidth - canvasWidth) * 0.5) + 'px';
var ctx = cnv.getContext("2d");
ctx.translate(0.5, 0.5);
gridContext();
The rest of the code is the same as your original code, i just changed the size of you squares to get quite the same visual aspect.
ctx.beginPath();
for (var i=60; i<canvasHeight; i+=60) {
ctx.moveTo(0,i);
ctx.lineTo(canvasWidth,i);
}
for (i=60; i<canvasWidth; i+=60) {
ctx.moveTo(i,0);
ctx.lineTo(i,canvasHeight);
}
ctx.strokeStyle="#000";
ctx.stroke();
ctx.closePath();
With those changes we go from :
to :
Edit : to ensure rounding, in fact i think most convenient is to inject the context and change moveTo, lineTo :
function gridContext() {
var oldMoveTo = CanvasRenderingContext2D.prototype.moveTo;
CanvasRenderingContext2D.prototype.moveTo = function (x,y) {
x |= 0; y |= 0;
oldMoveTo.call(this, x, y);
}
var oldLineTo = CanvasRenderingContext2D.prototype.lineTo;
CanvasRenderingContext2D.prototype.lineTo = function (x,y) {
x |= 0; y |= 0;
oldLineTo.call(this, x, y);
}
}
Obviously, you must do this for all drawing functions you need.
When drawing lines on a canvas, the line itself is exactly on the pixel grid. But because the line is one pixel wide, half of it appears in each of the pixels to either side of the grid, resulting in antialising and a line that is basically 50% transparent over two pixels.
Instead, offset your line by 0.5 pixels. This will cause it to appear exactly within the pixel.
Demo

Html5 canvas pattern drawing delay

i draw a canvas(aka canvas 1) with image() then rotate it 25 degree. then i take rotated canvas to make a pattern for another canvas(aka canvas 2). then i draw this . and fill the fillstyle with newly created pattern. i noticed if alert in the middle of below code
finalsleeve_ctx.globalCompositeOperation = "source-atop";
/*****************************************
alert(sleeve.toDataURL('image/png'));
*****************************************/
var pattern = finalsleeve_ctx.createPattern(sleeve, 'repeat');
then firefox gives a correct output but if i dont do alert it does not give me correct output. crome not showing me correct output.
do i need to delay ?
here is what i have tried.
HTML
<div >
<canvas id="sleeve" width=436 height=567></canvas>
<canvas id="finalsleeve" width=436 height=567 ></canvas>
</div>
JS
var sleeve = document.getElementById('sleeve');
var sleeve_ctx = sleeve.getContext('2d');
var finalsleeve = document.getElementById('finalsleeve');
var finalsleeve_ctx = finalsleeve.getContext('2d');
function rotator2(var2,var3)
{
sleeve.width=sleeve.width;
var imageObj_rotator2 = new Image();
imageObj_rotator2.onload = function ()
{
var pattern_rotator2 = sleeve_ctx.createPattern(imageObj_rotator2, "repeat");
sleeve_ctx.fillStyle = pattern_rotator2;
sleeve_ctx.rect(0, 0, sleeve.width, sleeve.height);
sleeve_ctx.rotate(var3 * Math.PI/180);
sleeve_ctx.fill();
}
imageObj_rotator2.src = var2;
}
function drawSleeve()
{
finalsleeve.width = finalsleeve.width;
var imgsleeve = new Image();
imgsleeve.src="http://i.stack.imgur.com/FoqGC.png";
finalsleeve_ctx.drawImage(imgsleeve,0,0);
finalsleeve_ctx.globalCompositeOperation = "source-atop";
alert(sleeve.toDataURL('image/png'));
var pattern = finalsleeve_ctx.createPattern(sleeve, 'repeat');
finalsleeve_ctx.rect(0, 0, sleeve.width, sleeve.height);
finalsleeve_ctx.fillStyle = pattern;
finalsleeve_ctx.fill();
finalsleeve_ctx.globalAlpha = .10;
finalsleeve_ctx.drawImage(imgsleeve, 0, 0);
finalsleeve_ctx.drawImage(imgsleeve, 0, 0);
finalsleeve_ctx.drawImage(imgsleeve, 0, 0);
}
rotator2('http://i.stack.imgur.com/fvpMN.png','25');
drawSleeve();
Here is fiddle.
http://jsfiddle.net/EbBHz/
EDITED
Sorry, I completely misunderstood your question. I just now went back and saw the last question you posted and the goal you are trying to achieve.
To get the functionality you desire you can just create one function, you don't need two. Instead of using a second canvas in the HTML I created a temporary one using javascript.
Here is the simplified and functional code
<canvas id="sleeve" width='436' height='567'></canvas>
var sleeve = document.getElementById('sleeve');
var ctx = sleeve.getContext('2d');
function rotator2(var2, var3) {
// Draw the original sleeves
var imageObj_rotator2 = new Image();
imageObj_rotator2.src = var2;
imageObj_rotator2.onload = function () {
var imgsleeve = new Image();
imgsleeve.src="http://i.stack.imgur.com/FoqGC.png";
ctx.drawImage(imgsleeve,0,0);
// Create a second temporary canvas
var pattern = document.createElement('canvas');
pattern.width = 500;
pattern.height = 500;
var pctx = pattern.getContext('2d');
// Make the pattern that fills the generated canvas
var pattern_rotator2 = pctx.createPattern(imageObj_rotator2, "repeat");
pctx.fillStyle = pattern_rotator2;
pctx.rotate(var3 * Math.PI / 180);
// Fill the generated canvas with the rotated image pattern we just created
pctx.fillRect(0, 0, pattern.width, pattern.height);
// Create a pattern of the generated canvas
var patterned = ctx.createPattern(pattern, "repeat");
// Fills in the non-transparent part of the image with whatever the
// pattern from the second canvas is
ctx.globalCompositeOperation = "source-in";
ctx.fillStyle = patterned;
ctx.fillRect(0, 0, sleeve.width, sleeve.height);
}
}
rotator2('http://i.stack.imgur.com/fvpMN.png', '45')
The technique works alright, but only for certain angles. Here is the demo set to 45 degrees. As you can see, there is a problem: part of the sleeve is whited out. However, if you change the degree to 15 like this it works just fine. This is because when the image is being rotated in the created canvas it leaves white space before repeating. To see this issue first hand, change the width and the height of the created canvas to 30 (the default width/height of the image) like this
Note: You may have to click run once the jsfiddle tab is open, canvases don't like generating content when another tab is focused
I tried problem solving the issue including
Making the generated canvas really large (which works but KILLS load
time/crashes page sometimes)
Translating the picture in the generated canvas after rotating it
which didn't work like I had hoped
Coming up with a function to change the width/height to cover the
entire first canvas based on the rotated second-canvas-dimensions, which is by far the most promising, but I don't have the time or desire to work out a good solution
All that being said if the angle HAS to be dynamic you can work on a function for it. Otherwise just use a workaround angle/generated canvas dimensions
final result> Here is a working solution for fill rotated pattern without white at any angle
var sleeve = document.getElementById('sleeve');
var ctx = sleeve.getContext('2d');
function rotator2(var2, var3) {
var x =0;
var y=0;
//pattern size should be grater than height and width of object so that white space does't appear.
var patternSize = sleeve.width+ sleeve.height;
// Draw the original sleeves
var imageObj_rotator2 = new Image();
imageObj_rotator2.src = var2;
imageObj_rotator2.onload = function () {
var imgsleeve = new Image();
imgsleeve.src="http://i.stack.imgur.com/FoqGC.png";
ctx.drawImage(imgsleeve,0,0);
// Create a second temporary canvas
var pattern = document.createElement('canvas');
pattern.width = sleeve.width;
pattern.height = sleeve.height;
var pctx = pattern.getContext('2d');
// Make the pattern that fills the generated canvas
var pattern_rotator2 = pctx.createPattern(imageObj_rotator2, "repeat");
pctx.fillStyle = pattern_rotator2;
//moving rotation point to center of target object.
pctx.translate(x+ sleeve.width/2, y+sleeve.height/2);
pctx.rotate(var3 * Math.PI / 180);
// Fill the generated canvas with the rotated image pattern we just created and expanding size from center of rotated angle
pctx.fillRect(-patternSize/2, -patternSize/2, patternSize, patternSize);
// Create a pattern of the generated canvas
var patterned = ctx.createPattern(pattern, "no-repeat");
// Fills in the non-transparent part of the image with whatever the
// pattern from the second canvas is
ctx.globalCompositeOperation = "source-in";
ctx.fillStyle = patterned;
ctx.fillRect(x, y, sleeve.width, sleeve.height);
}
}
rotator2('http://i.stack.imgur.com/fvpMN.png', '50')

Categories

Resources