html5 canvas - emulate 3d rotation of image - html

I have an image which has square shape. I want to rotate and squeeze it to get 3d effect like on the pictures below:
Source image:
Rotate to 0 degrees and squeeze:
Rotate to 45 degrees and squeeze:
Something like this.
I have played around Math and tried to change Width and Height of the image by multiplying to Sin and Cos of angle.
var w = image.width*Math.cos(angle* TO_RADIANS);
var h = image.height*Math.sin(angle* TO_RADIANS);
h=h*2/3; //squeezing the height
ctx.drawImage(image, 0, 0, w, h);
But I am not good at mathematics, so I hope somebody may help me to solve this issue.

I've resolved my problem. I squeezed my image in photoshop. So, the image became 300x150 size and looked like the second picture above. Then I applied a function below anytime, when I needed to redraw the image according to the angle:
var TO_RADIANS = Math.PI/180;
ctx.save();
ctx.translate(x, y);
ctx.rotate(angle * TO_RADIANS);
var w = image.width-Math.abs((image.width/2)*Math.sin(angle* TO_RADIANS));
var h = image.height+Math.abs((image.height)*Math.sin(angle * TO_RADIANS));
ctx.translate(-w/2, -h/2);
ctx.drawImage(image, 0, 0, w, h);
ctx.restore();
Now it works pretty well.

You should look into a transform matrix.
Something like this:
ctx.transform(1, 0.6, -.8, .5, 20, 0);
http://jsfiddle.net/ericjbasti/nNZLC/
some links for you :
http://www.w3schools.com/tags/canvas_transform.asp
https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/HTML-canvas-guide/MatrixTransforms/MatrixTransforms.html

Related

How to transform and draw image on html canvas?

I wanna transform and draw an image on canvas. First I'm transforming an image over canvas through touch gestures and that transformation is handled through css
and controlled by 4 variables rotation, deltaX, deltaY and scale where deltaX and deltaY are positions of center an image. But after finalizing that transformation, I wanna draw that image with same transformations on the canvas. I tried the following but didn't got expected result:
img.onload = () => {
// this.ctx.setTransform(this.transformBlock.scale, 0, 0, this.transformBlock.scale, x, y);
this.ctx.rotate(this.transformBlock.rotation);
this.ctx.scale(this.transformBlock.scale, this.transformBlock.scale);
this.ctx.translate(this.transformBlock.deltaX, this.transformBlock.deltaY);
this.ctx.drawImage(img, -width / 2, -height / 2);
};
Suppose if my transformation is this: rotation = 90, scale = 2, delX = 180, delY = 320 than my image is 90 deg rotated clockwise with width and height doubled(scale=2) and center of image is at (180px, 320px) (delX, delY). But while drawing on canvas scale is more than 2, most of the image went out of the screen and rotation is not 90 deg. If anybody tell me how to obtain same expected behavior on canvas. It would be great help.
Thanks.

Getting the right width and height of a canvas element

I am having difficulties with setting the correct width and height of my canvas element.
I have a ball, that I'd like to bounce back whenever it hits a screen boundary by changing it's vertical velocity. It works, but instead of moving back as soon as it hits the edge of the screen, it goes on for a couple of seconds and THEN moves back. I have these variables to determine the viewport's size:
var left = 0,
right = canvas.width,
top = 0,
bottom = canvas.height;
If my ball's x or y positions are outside these boundaries, the velocity should be changed to a negative one. However, during my animation I console.log it's x position and by the time it reaches the right edge of the screen the value is around 600, which is really strange, since I'm on a 1366x768px monitor.
Also, it doesnt't fully reach the left screen edge, but bounces off like 50px from it.
Any ideas are really appreciated, because I've been stuck on this for quite some time.
You can see a working example here: http://codepen.io/gbnikolov/pen/puiwk
Update your draw to the following.
Ball.prototype.draw = function(ctx) {
ctx.save();
// you've translated to the x and y position of the ball.
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.scale(this.scaleX, this.scaleY);
ctx.lineWidth = this.lineWidth;
ctx.fillStyle = this.color;
ctx.strokeStyle = this.strokeColor;
ctx.beginPath();
// Draw at 0,0 since we are already translated to the x and y.
ctx.arc(0, 0, this.radius, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.restore();
}
Live Demo
Your problem is in the draw method, you're translating the context and then making the arc at the x and y of the ball so if you translate to 20, 20 for example and then draw at 20,20 your ball is actually at 40,40.

Gradient stroke along canvas arc disappearing when context translates to center

I am very new to canvas drawings. I am trying to apply gradient along the arc of a circle. I can get it to appear fine when I draw the arc with center offset from the context coordinates. Lets say centerX and centerY denote the center of the canvas. I can get a gradient arc using context.arc(centerX, centerY, radius, ......).
Working example: http://jsfiddle.net/m5Pmb/
But when I try to draw the arc around the context coordinates, the gradient disappears. For example, I take the above working jsfiddle example, do context.translate(centerX, centerY), then do context.arc(0,0,radius,......), the resulting arc does not have any gradient on it.
Example here: http://jsfiddle.net/N6NMB/
In my case, I need to spin the resulting circle around its axis using context.rotate(), so I must translate to the center and draw circle around (0,0). But I cant understand why the gradient disappears when trying to draw arc around context's (0,0) point. Any insight would be really helpful.
Since you are translating the context centerX and centerY are not where you think anymore.
When you translate the context you are saying you want that x and y to be the new 0,0. So now your 0,0 is in the center of the canvas, so centerX and centerY are offset by themselves putting them further away from the center.
One method you can use is the following
var grad = context.createLinearGradient(
-radius,
radius / 2,
radius,
radius / 2
);
live Demo
The above works because its called after you translate the context, so centerX and centerY (like I said previously) are 0,0 meaning they don't need to be referenced in that function.
The gradients that you create will be painted with the actual transform in use.
So if you are not using any transform, no need to wonder anything : define your gradient where you're about to draw, and you'll be fine.
If you are using transform, you must think of the coordinates of the gradient as relative to the point/angle/scale when you'll use them.
To explain further, i modified your example and used a radial gradient.
I created a normalized gradient : it is defined in between 0.0 and 1.0 radius, meaning it will have its x and y in [-1; 1].
var eyeGrad = context.createRadialGradient(0, 0, 0, 0, 0, 1.0);
Then to use the gradient i must :
1) translate to be in the center of the figure i want to draw.
2) scale to have normalized coordinates.
function drawEye(x, y, r) {
context.save();
//translate context to center
context.translate(x, y);
// scale to radius
context.scale(r, r);
context.beginPath();
// draw an arc with radius of 1
context.arc(0, 0, 1, 0, 2 * Math.PI, false);
context.fillStyle = eyeGrad;
context.fill();
context.restore();
}
fiddle is here :
http://jsfiddle.net/gamealchemist/N6NMB/3/
Result for :
drawEye(100, 100, 40);
drawEye(250, 120, 20);

Drawing a dot on HTML5 canvas [duplicate]

This question already has answers here:
What's the best way to set a single pixel in an HTML5 canvas?
(14 answers)
Closed 7 years ago.
Drawing a line on the HTML5 canvas is quite straightforward using the context.moveTo() and context.lineTo() functions.
I'm not quite sure if it's possible to draw a dot i.e. color a single pixel. The lineTo function wont draw a single pixel line (obviously).
Is there a method to do this?
For performance reasons, don't draw a circle if you can avoid it. Just draw a rectangle with a width and height of one:
ctx.fillRect(10,10,1,1); // fill in the pixel at (10,10)
If you are planning to draw a lot of pixel, it's a lot more efficient to use the image data of the canvas to do pixel drawing.
var canvas = document.getElementById("myCanvas");
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
var ctx = canvas.getContext("2d");
var canvasData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
// That's how you define the value of a pixel
function drawPixel (x, y, r, g, b, a) {
var index = (x + y * canvasWidth) * 4;
canvasData.data[index + 0] = r;
canvasData.data[index + 1] = g;
canvasData.data[index + 2] = b;
canvasData.data[index + 3] = a;
}
// That's how you update the canvas, so that your
// modification are taken in consideration
function updateCanvas() {
ctx.putImageData(canvasData, 0, 0);
}
Then, you can use it in this way :
drawPixel(1, 1, 255, 0, 0, 255);
drawPixel(1, 2, 255, 0, 0, 255);
drawPixel(1, 3, 255, 0, 0, 255);
updateCanvas();
For more information, you can take a look at this Mozilla blog post : http://hacks.mozilla.org/2009/06/pushing-pixels-with-canvas/
It seems strange, but nonetheless HTML5 supports drawing lines, circles, rectangles and many other basic shapes, it does not have anything suitable for drawing the basic point. The only way to do so is to simulate a point with whatever you have.
So basically there are 3 possible solutions:
draw point as a line
draw point as a polygon
draw point as a circle
Each of them has their drawbacks.
Line
function point(x, y, canvas){
canvas.beginPath();
canvas.moveTo(x, y);
canvas.lineTo(x+1, y+1);
canvas.stroke();
}
Keep in mind that we are drawing to South-East direction, and if this is the edge, there can be a problem. But you can also draw in any other direction.
Rectangle
function point(x, y, canvas){
canvas.strokeRect(x,y,1,1);
}
or in a faster way using fillRect because render engine will just fill one pixel.
function point(x, y, canvas){
canvas.fillRect(x,y,1,1);
}
Circle
One of the problems with circles is that it is harder for an engine to render them
function point(x, y, canvas){
canvas.beginPath();
canvas.arc(x, y, 1, 0, 2 * Math.PI, true);
canvas.stroke();
}
the same idea as with rectangle you can achieve with fill.
function point(x, y, canvas){
canvas.beginPath();
canvas.arc(x, y, 1, 0, 2 * Math.PI, true);
canvas.fill();
}
Problems with all these solutions:
it is hard to keep track of all the points you are going to draw.
when you zoom in, it looks ugly
If you are wondering, what is the best way to draw a point, I would go with filled rectangle. You can see my jsperf here with comparison tests
In my Firefox this trick works:
function SetPixel(canvas, x, y)
{
canvas.beginPath();
canvas.moveTo(x, y);
canvas.lineTo(x+0.4, y+0.4);
canvas.stroke();
}
Small offset is not visible on screen, but forces rendering engine to actually draw a point.
The above claim that "If you are planning to draw a lot of pixel, it's a lot more efficient to use the image data of the canvas to do pixel drawing" seems to be quite wrong - at least with Chrome 31.0.1650.57 m or depending on your definition of "lot of pixel". I would have preferred to comment directly to the respective post - but unfortunately I don't have enough stackoverflow points yet:
I think that I am drawing "a lot of pixels" and therefore I first followed the respective advice for good measure I later changed my implementation to a simple ctx.fillRect(..) for each drawn point, see http://www.wothke.ch/webgl_orbittrap/Orbittrap.htm
Interestingly it turns out the silly ctx.fillRect() implementation in my example is actually at least twice as fast as the ImageData based double buffering approach.
At least for my scenario it seems that the built-in ctx.getImageData/ctx.putImageData is in fact unbelievably SLOW. (It would be interesting to know the percentage of pixels that need to be touched before an ImageData based approach might take the lead..)
Conclusion: If you need to optimize performance you have to profile YOUR code and act on YOUR findings..
This should do the job
//get a reference to the canvas
var ctx = $('#canvas')[0].getContext("2d");
//draw a dot
ctx.beginPath();
ctx.arc(20, 20, 10, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();

html5 canvas prevent linewidth scaling

If I draw a rectangle of say linewidth=2 and then scale it to double the size of the rectangle, I get a rectangle that has its border double the size of the initial linewidth.
Is there a way to keep the linewidth to the perceived size of 2 or the original size.
In short, I want to just scale the size of the rectangle but keep the linewidth visually of size 2.
I tried setting the linewidth before and after the scale(2,2) command but the border width also increases.
One option is to divide the linewidth by the scale factor and this will work if the x and y scale factors are the same.
I don't have the option to scale the rectangle width and height and I need to use the scale command as I have other objects within the rectangle that need the scaling.
You can define path with transformation and stroke it without one. That way line width won't be transformed.
Example:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.save(); //save context without transformation
ctx.scale(2, 0.5); //make transformation
ctx.beginPath(); //define path
ctx.arc(100, 75, 50, 0, 2 * Math.PI);
ctx.restore(); //restore context without transformation
ctx.stroke(); //stroke path
<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;">
The lineWidth should be scaled down beforehand.
ctx.lineWidth = 2 / Math.max(scaleX, scaleY);
ctx.scale(scaleX, scaleY);
ctx.fillRect(50, 50, 50, 50);
Ok, you have a couple of options:
You can do your own scaling of coordinates and dimensions, e.g.
ctx.strokeRect( scaleX * x, scaleY * y, scaleX * width, scaleY * height) ;
And you'll need to apply the scaling to all the other objects too.
Alternatively you could apply the scaling but not rely on lineWidth for drawing the border of the rectangle. A simple method would be to draw the rectangle by filling the correct region and then removing the interior, minus the border, like so:
var scaleX = 1.5, scaleY = 2;
var lineWidth = 2;
ctx.scale(scaleX, scaleY);
ctx.fillStyle = "#000";
ctx.fillRect(100, 50, 100,
ctx.clearRect(100+lineWidth/scaleX, 50+lineWidth/scaleY, 100-(2*lineWidth)/scaleX, 60-(2*lineWidth)/scaleY);