Canvas rotation doesn't work properly - javascript

I am having troubles trying to rotate a rectangle via JavascriptCanvas API.
Here is the code:
G = {};
// get canvas context
G.ctx = document.getElementById('canvas').getContext('2d');
var x = 200;
var y = 100;
var w = 30;
var h = 70;
G.ctx.fillRect(x, y, w, h);
// Why is this not working??
G.ctx.save();
G.ctx.translate(x, y);
G.ctx.rotate(30*(Math.PI/180));
G.ctx.fillRect(x, y, w, h);
G.ctx.restore();
The code only draws the first rectangle for some reason.
Here is the JSfiddle: http://jsfiddle.net/5YZbd/1/
Any clarification is really welcome!

I figured it out.
As soon as I translate the canvas to rectangle's x/y - its position should be referred to as 0/0, cause thats where the canvas origin is after the translation.
Here is the working code:
G.ctx.save();
G.ctx.translate(x, y);
G.ctx.rotate(30*(Math.PI/180));
G.ctx.fillRect(0, 0, w, h);
G.ctx.restore();

Related

Adding outer stroke to colliding canvas rectangles

I'm adding multiple rectangles in canvas which could collide with each other. The outer stroke should be displayed on the outer part of both rectangles or the rectangle shapes should be merged in to one producing the expected result.
See picture bellow
It has to be cut because it will display the content under the canvas. See live example with background image: https://jsfiddle.net/0qpgf5un/
In the code example bellow rectangles are being added on top of each other as you can see in the first example of the picture.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var offsetX = 150;
var offsetY = 150;
var w = 200;
var h = 100;
ctx.fillStyle = "red";
ctx.rect(0, 0, 600, 600);
ctx.fill();
ctx.clearRect(offsetX,offsetY, w, h);
ctx.strokeRect(offsetX, offsetY, w, h);
ctx.clearRect(offsetX-50,offsetY+50, w, h);
ctx.strokeRect(offsetX-50, offsetY+50, w, h);
Is there ways to achieve it without writing complex calculations of each path, since the collision of rectangles can be unintentional and diverse ?
Edit:
What I am trying to achieve is a similar functionality like in youtube's feedback form where when editing screenshot you can highlight items and the border then is merged.
Just add one more clearRect() (the first one)
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var offsetX = 150;
var offsetY = 150;
var w = 200;
var h = 100;
ctx.fillStyle = "red";
ctx.rect(0, 0, 600, 600);
ctx.fill();
ctx.clearRect(offsetX,offsetY, w, h);
ctx.strokeRect(offsetX, offsetY, w, h);
ctx.clearRect(offsetX-50,offsetY+50, w, h);
ctx.strokeRect(offsetX-50, offsetY+50, w, h);
ctx.clearRect(offsetX,offsetY, w, h);
https://jsfiddle.net/kt3yjhpc/
You can skip clearing the first rectangle and then clear it after you stroke the second one.
The clearPrev function will clear the area inside the strokes of the initial rectangle.
let canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
offsetX = 70,
offsetY = 20,
w = 200,
h = 100,
strokeWidth = 5;
ctx.fillStyle = '#F00'
ctx.rect(0, 0, 600, 600);
ctx.fill();
ctx.strokeStyle = '#0FF';
ctx.lineWidth = strokeWidth;
//ctx.clearRect(offsetX, offsetY, w, h); <-- Do not need to do this, if we clear below...
ctx.strokeRect(offsetX, offsetY, w, h);
ctx.clearRect(offsetX - 50, offsetY + 50, w, h);
ctx.strokeRect(offsetX - 50, offsetY + 50, w, h);
clearPrev(ctx, offsetX, offsetY, w, h); // Clear previous
function clearPrev(ctx, x, y, w, h) {
let startOffset = Math.round(ctx.lineWidth / 2) - 1,
endOffset = strokeWidth - 1;
ctx.clearRect(x + startOffset, y + startOffset, w - endOffset, h - endOffset);
}
<canvas id="canvas" width="290" height="190"></canvas>
When you want to clear the canvas with complex shapes, forget about clearRect, it's not the only one able to produce transparent pixels.
Instead, have a look at compositing.
So your shape is really border-line, but I think you'll benefit from using this already:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var offsetX = 150;
var offsetY = 150;
var w = 200;
var h = 100;
ctx.lineWidth = 2;
ctx.fillStyle = "red";
ctx.fillRect(0, 0, 600, 600);
// declare our complex shape as a single sub-path
ctx.beginPath()
ctx.rect(offsetX,offsetY, w, h);
ctx.rect(offsetX-50, offsetY+50, w, h);
// now we can paint it
// first the stroke, because we want to erase what's inside the fill-area
ctx.stroke();
// now to erase, we switch to destination-out compositing mode
ctx.globalCompositeOperation = 'destination-out';
// fill the inner path
ctx.fill();
// we're done
// If you wish to go back to normal mode later
ctx.globalCompositeOperation = 'source-over';
body { background: linear-gradient(blue,yellow); }
<canvas id="canvas" width="600" height="600"></canvas>

Making Canvas Sprites Centered on Coordinates

I am working on a Canvas Game on HTML5 Canvas with Vanilla JS.
For some reason I am noticing that when I set the player sprite to be drawn in at on the x-axis at x=0, the player appears indented to the right.
(this appears to be disrupting my collision detection)
I have the same issue with other sprites I have generated with the piskel app. Another sprite I used from another creator didn't have this issue.
Does anyone have any suggestions?
Here is a link to my game: http://zcbuhler.github.io/spaceDrift
The player should be at 0 on the x-axis for the starting point, but as you can see appears to be tabbed over.
General purpose sprite rendering
I render sprites with the following function.
// assumes ctx is scoped and is the rendering 2d context
function drawSprite(img, x, y, scale, rotate, alpha){
var w = img.width;
var h = img.height;
ctx.setTransform(scale, 0, 0 ,scale, x, y);
ctx.rotate(rotate);
ctx.globalAlpha = alpha;
ctx.drawImage(img, 0, 0, w, h, -w/2,-h/2, w, h);
}
It draws the sprite with its center at x,y. It is scaled and and rotated and its alpha is set. On a average laptop and on firefox it can do 2000+ sprites in realtime. On chrome its about 1000+
To set the center point use
// assumes ctx is scoped and is the rendering 2d context
function drawSpriteCenter(img, x, y, cx, cy, scale, rotate, alpha){
var w = img.width;
var h = img.height;
ctx.setTransform(scale, 0, 0 ,scale, x, y);
ctx.rotate(rotate);
ctx.globalAlpha = alpha;
ctx.drawImage(img, 0, 0, w, h, -cx,-cy, w, h);
}
where cx, and cy s the center point of the sprite. (the point around which it rotates)
To draw a sprite with a center cx,cy at x,y and a scale for x and y, rotated with alpha.
// assumes ctx is scoped and is the rendering 2d context
function drawSpriteFull(img, x, y, cx, cy, scaleX, scaleY, rotate, alpha){
var w = img.width;
var h = img.height;
ctx.setTransform(scaleX, 0, 0 ,scaleY, x, y);
ctx.rotate(rotate);
ctx.globalAlpha = alpha;
ctx.drawImage(img, 0, 0, w, h, -cx, -cy, w, h);
}
The functions modify the current transform and alpha. To reset the canvas state once you are done rendering the sprites you can use
function resetCtx(){
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.globalAlpha = 1;
}
You should be able to correct the player's position like #Bangkokian pointed out.
Simply wrap that in a function like so:
function positionPlayer(x, y) {
player.pos = [x -= (player.w / 2), y];
}
or even make that a method of the players object.
The collision detection could be solved similarly by altering the 'checkCollision' function to something like:
var checkCollision = function(rect1, rect2) {
// debugger
// centers
var rect1CenteredPos = [rect1.pos[0] - (rect1.w / 2), rect1.pos[1] - (rect1.h / 2)];
var rect2CenteredPos = [rect2.pos[0] - (rect2.w / 2), rect2.pos[1] - (rect2.h / 2)];
// console.log(bulletsArray.length);
if (rect1CenteredPos[0] < rect2CenteredPos[0] + rect2.w &&
rect1CenteredPos[0] + rect1.w > rect2CenteredPos[0] &&
rect1CenteredPos[1] < rect2CenteredPos[1] + rect2.h &&
rect1.h + rect1CenteredPos[1] > rect2CenteredPos[1]) {
return true;
}
return false;
}

Rotating element on canvas object, relative to parent

I'm trying to draw a pair of skis (two rectangles) on a skier (a square) at varying rotations. I don't quite understand how you line up rotated elements on a canvas as you rotate the whole canvas, not just the object.
At the moment I have this:
ctx.fillStyle = 'rgba(0,0,0,0.8)';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = skiier.color;
ctx.fillRect(skiier.x, skiier.y, skiier.width, skiier.height);
ctx.fillStyle = '#00f';
var angle = 20;
ctx.rotate(angle*Math.PI/180);
ctx.fillRect(skiier.x, skiier.y,100,10);
ctx.fillRect(skiier.x, skiier.y + 20,100,10);
ctx.rotate(-angle*Math.PI/180);
Which gives me this:
But what I'd like to do is the following:
Bearing in mind the x and y coords of the skier is constantly changing, how can I adjust and position the skis relative to him?
I have a demo here if it helps:
http://codepen.io/EightArmsHQ/pen/cfa7052ed205b664b066450910c830c5?editors=001
You should consider the context save, restore and translate as follows:
// ...
var angle = 20;
ctx.save(); // save the state of the ctx
ctx.translate(skiier.x, skiier.y); // translate your context point to be the same as skiier.
ctx.rotate(angle * Math.PI / 180);
ctx.fillRect(-25, 0, 100, 10); // You can draw from new context point.
ctx.fillRect(-25, 20, 100, 10); // Same here.
// Instead of rotating back, use restore()...
// Useful to restore all ctx options as they were before the save()
ctx.restore();
To rotate around a point: you need to translate context to the point, rotate context, translate context back.
For example...
ctx.fillStyle = 'rgba(0,0,0,0.8)';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = skiier.color;
ctx.fillRect(skiier.x, skiier.y, skiier.width, skiier.height);
ctx.fillStyle = '#00f';
var angle = 20;
ctx.translate(skiier.x, skiier.y);
ctx.rotate(angle*Math.PI/180);
ctx.translate(-skiier.x, -skiier.y);
ctx.fillRect(skiier.x, skiier.y,100,10);
ctx.fillRect(skiier.x, skiier.y + 20,100,10);
ctx.rotate(-angle*Math.PI/180);

Transfer only a Part of Canvas to a New Canvas

I have a canvas with an id of cnv.
<canvas id='cnv'></canvas>
The dark rectangle is the whole canvas. I want to create another canvas that contains only the white region in the old canvas. How will I transfer a part of a canvas to a new canvas?
var cnv = document.getElementById('cnv');
I don't know what to do next in my code above.
Assuming you have the region specificed in x, y, width and height, you can do:
function regionToCanvas(canvas, x, y, w, h) {
var c = document.createElement("canvas"), // create new canvas
ctx = c.getContext("2d"); // context for new canvas
c.width = w; // set size = w/h
c.height = h;
ctx.drawImage(canvas, x, y, w, h, 0, 0, w, h); // draw in region at (0,0)
return c; // return canvas
}
Then call, example:
var newCanvas = regionToCanvas(cnv, x, y, width, height);
document.body.appendChild(newCanvas); // add to DOM

How to properly clear a rotated HTML5 canvas context?

I need to clear a rotated rectangle and draw it again on a canvas, either at the same place or elsewhere. The problem is, the cleared part doesn't match the rectangle's bounds, leaving dirty bits behind.
var cvs = document.getElementById('testcanvas');
var ctx = cvs.getContext('2d');
var rectColor = 'green';
var x = 300;
var y = 10;
var width = 200;
var height = 150;
var rotation = 0;
setInterval(animate, 100);
function animate(){
clearRect();
update();
drawRect();
}
function clearRect(){
ctx.save();
ctx.rotate(rotation);
ctx.clearRect(x, y, width, height);
ctx.restore();
}
function update(){
rotation += 0.1;
x++;
}
function drawRect(){
ctx.save();
ctx.fillStyle = rectColor;
ctx.rotate(rotation);
ctx.fillRect(x, y, width, height);
ctx.restore();
}
​
http://jsfiddle.net/MFz2z/15/
Another issue, clearRect() behaves differently on Firefox when the canvas is rotated, by clearing the whole unrotated space used by the rotated rectangle.
http://jsfiddle.net/MFz2z/17/
Is there any workaround to this, other than clearing the whole canvas and drawing everything again ? I use Chrome 22 and Firefox 15.0.1.
It could be due to the anti-aliasing of the pixels that create the extra tidbits.
You could use this http://jsfiddle.net/MFz2z/28/ and basically clear 1 more pixel from each side.

Categories

Resources