I have an array like this, the idea is to output images in different directions and flip them vertically and horizonatly on a canvas by scaling.
[{
"pos":{
"x":411,
"y":401.5
},
"scale":{
"x":1,
"y":1
}
},{
"pos":{
"x":411,
"y":271.59625
},
"scale":{
"x":-1,
"y":1
}
}]
The problem is that I'm scaling the canvas instead of the images, the canvas is multiple times bigger than the images i'm placing on it.
images.forEach((image) => {
// center borde köars innan loopen egentligen
let pos = center(image.pos)
cc.save()
cc.scale(image.scale.x, image.scale.y)
cc.drawImage(window.video, pos.x, pos.y)
cc.restore()
})
How do I scale the image, called window.video, instead of the entire canvas?
To render a scaled image on the canvas.
function drawImage(image,x,y,scaleX,scaleY){
ctx.setTransform(scaleX, 0, 0, scaleY, x, y); // set scale and translation
ctx.drawImage(image,0,0);
}
// when done drawing images you need to reset the transformation back to default
ctx.setTransform(1,0,0,1,0,0); // set default transform
If you want the image flipped but still drawn at the same top left position use
function drawImage(image, x, y, scaleX, scaleY){
ctx.setTransform(scaleX, 0, 0, scaleY, x, y); // set scale and translation
x = scaleX < 0 ? -image.width : 0; // move drawing position if flipped
y = scaleY < 0 ? -image.height : 0;
ctx.drawImage(image, x, y);
}
And to draw about the center of the image
function drawImage(image, x, y, scaleX, scaleY){ // x y define center
ctx.setTransform(scaleX, 0, 0, scaleY, x, y); // set scale and translation
ctx.drawImage(image, -image.width / 2, -image.height / 2);
}
EDIT: As noted below, this doesn't work with negative values...
drawImage can take 2 extra arguments for sizing the image, so the following should work:
images.forEach((image) => {
// center borde köars innan loopen
let pos = center(image.pos)
cc.save()
cc.drawImage(window.video, pos.x, pos.y, window.video.width * image.scale.x, window.video.height * image.scale.y)
cc.restore()
})
Documentation: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Using_images
Related
I am experimenting with animation in <canvas> and can't work out how to draw an image at an angle. The desired effect is a few images drawn as usual, with one image rotating slowly. (This image is not at the centre of the screen, if that makes any difference).
You need to modify the transformation matrix before drawing the image that you want rotated.
Assume image points to an HTMLImageElement object.
var x = canvas.width / 2;
var y = canvas.height / 2;
var width = image.width;
var height = image.height;
context.translate(x, y);
context.rotate(angleInRadians);
context.drawImage(image, -width / 2, -height / 2, width, height);
context.rotate(-angleInRadians);
context.translate(-x, -y);
The x, y coordinates is the center of the image on the canvas.
It is interesting that the first solution worked for so many people, it didn't give the result I needed.
In the end I had to do this:
ctx.save();
ctx.translate(positionX, positionY);
ctx.rotate(angle);
ctx.translate(-x,-y);
ctx.drawImage(image,0,0);
ctx.restore();
where (positionX, positionY) is the coordinates on the canvas that I want the image to be located at and (x, y) is the point on the image where I want the image to rotate.
I have written a function (based on Jakub's answer) that allows user to paint an image in a X,Y position based on a custom rotation in a custom rotation point:
function rotateAndPaintImage ( context, image, angleInRad , positionX, positionY, axisX, axisY ) {
context.translate( positionX, positionY );
context.rotate( angleInRad );
context.drawImage( image, -axisX, -axisY );
context.rotate( -angleInRad );
context.translate( -positionX, -positionY );
}
Then you can call it like this:
var TO_RADIANS = Math.PI/180;
ctx = document.getElementById("canvasDiv").getContext("2d");
var imgSprite = new Image();
imgSprite.src = "img/sprite.png";
// rotate 45º image "imgSprite", based on its rotation axis located at x=20,y=30 and draw it on context "ctx" of the canvas on coordinates x=200,y=100
rotateAndPaintImage ( ctx, imgSprite, 45*TO_RADIANS, 200, 100, 20, 30 );
I'm attempting to flip my canvas horizontally as I follow along with this awesome mario bros tutorial. As I've also seen in many other answers about flipping a canvas, I added these lines updating the context:
define(name, x, y, width, height) {
const { image, } = this;
const buffer = document.createElement('canvas');
buffer.width = width;
buffer.height = height;
const context = buffer.getContext('2d');
context.scale(-1, 1); // <<< THIS ONE
context.translate(width, 0); // <<< AND THIS ONE
context.drawImage(
image,
x,
y,
width,
height, // what part of the image to draw
0, 0, width, height); // where to draw it
this.tiles.set(name, buffer); // store the info about the tile in our map
}
The code, previously, worked awesomely. But when I add these lines and refresh my browser, the entire canvas is gone! I can't imagine things have changed so much in the past 2 1/2 years since the video was made as to introduce a breaking change here?!?! (I would imagine it will have not changed at all!)
What's wrong?
I use this for doing what you want:
function horizontalFlip(img,x,y){
/* Move to x + image width */
context.translate(x+img.width, y);
/* scaleX by -1; this causes horizontal flip */
context.scale(-1,1);
/* Draw the image
No need for x,y since we've already translated */
context.drawImage(img,0,0);
/* Clean up - reset transformations to default */
context.setTransform(1,0,0,1,0,0);
}
This function works as expected and here a variation for sprites.
function flipSpriteHorizontal(img, x, y, spriteX, spriteY, spriteW, spriteH){
/* Move to x + image width
adding img.width is necessary because we're flipping from
the right side of the image so after flipping it's still at [x,y] */
context.translate(x + spriteW, y);
/* ScaleX by -1, this performs a horizontal flip */
context.scale(-1, 1);
/* Draw the image
No need for x,y since we've already translated */
context.drawImage(img,
spriteX, spriteY, spriteW, spriteH,
0, 0, spriteW, spriteH
);
/* Clean up - reset transformations to default */
context.setTransform(1, 0, 0, 1, 0, 0);
}
I am wondering how you can rotate an image by only using the transform function. From my understanding this is not possible, since the only things you can do with transform are the following:
Horizontal scaling
Horizontal skewing
Vertical skewing
Vertical scaling
Horizontal moving
Vertical moving
Source: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Transformations
And I don't see how any of these would be able to rotate the shape, is this even possible. I assume it must be possible, since rotate is in-fact a type transformation.
2D transform basics
6 values as 3 vectors
The transform is a set of 6 numbers. The 6 numbers as 3 pairs represent the direction and scale of the x axis, the direction and scale of the y axis, and the position of the origin.
Default transform AKA Identity matrix
The default transform (called the identity matrix) has the values ctx.setTransform(1, 0, 0, 1, 0, 0) meaning that
the x axis is 1 transformed pixel per CSS pixel in the direction {x: 1, y: 0} left to right
the y axis is 1 transformed pixel per CSS pixel in the direction {x: 0, y: 1} top to bottom
the origin is at pixel location {x: 0, y: 0} top left corner
Scaling
If we scale the transform we increase the length of the first two vectors. To scale by 2 the transform is ctx.setTransform(2, 0, 0, 2, 0, 0);
the x axis is 1 transformed pixel for every 2 CSS pixel in the x direction {x: 2, y: 0} left to right
the y axis is 1 transformed pixel for every 2 CSS pixel in the y direction {x: 0, y: 2} top to bottom
the origin is still top left {x: 0, y: 0}
Rotate and translation
If we want to rotate by 90deg a square 256 by 256 image then the transform is ctx.setTransform(0, 1, -1, 0, 256, 0)
the x axis is 1 transformed pixel per CSS pixel down in the y direction {x: 0, y: 1}
the y axis is 1 transformed pixel per CSS pixel across in the negative x direction {x: -1, y: 0} right to left
the origin (where the image 0, 0 will be on the canvas) is {x: 256, y: 0}
Thus if we run
ctx.setTransform(0, 1, -1, 0, 256, 0);
ctx.drawImage(myImage, 0, 0, 256, 256); // will draw image rotated 90deg CW
We get a rotated image.
A vector
A vector is two values that have a x and y value. The vector defines a direction and length.
Create a rotated unit vector
To convert a direction to a vector we use sin and cos
const myDirection = angle;
const myDirectionAsRadians = angle * (Math.PI / 180); // convert angle to radians
const x = Math.cos(myDirectionAsRadians)
const y = Math.sin(myDirectionAsRadians)
If we set myDirection to 90 (deg) then x = 0 and y = 1 pointing down the canvas
Using sin and cos creates a vector in any direction. It has a special property in that its length is always 1. We call such a vector a Unit vector. You may sometimes see a vector being normalized. This converts a vector of any length to a unit vector. It is done by dividing the vector x and y by its length.
function normalize(vector) {
const length = Math.hypot(vector.x, vector.y);
vector.x /= length;
vector.y /= length;
}
NOTE a vector with zero length eg x: 0, y:0 can not be normalized. Not because it has no length (the length is 0) but because it has no direction.
Scale a rotated vector
We can define an angle and a scale
const myDirection = -90;
const myDirectionAsRadians = -90 * (Math.PI / 180); // -90 as radians
const myScale = 2;
const x = Math.cos(myDirectionAsRadians) * myScale
const y = Math.sin(myDirectionAsRadians) * myScale
Now for -90 deg the vector is x = 0 and y = -2 pointing up and two CSS pixels long.
Quick rotate vector 90deg CW
For a uniform scale and rotation (the image is always square) all we need is a single vector. For example from the above. x = 0 and y = -2 (pointing up) can be rotated 90 CW by swapping the two components and negating the new x. eg xx = -y and y = x to get xx = 2 and y = 0 2 CSS pixels from left two right. Thus we have the direction and scale of both the x and y axis. With the y axis always 90 CW from the x.
Using the transform to draw
Create rotated and scaled transform
To create a transform that rotates any angle and scales by any amount
function scaleAndRotate(scale, rotate) { // rotate is in radians
// get direction and length of x axis
const xAX = Math.cos(rotate) * scale;
const xAY = Math.sin(rotate) * scale;
// get direction and length of y axis that is 90 deg CW of x axis and same length
const [yAX, yAY] = [-xAY, xAX]; // swap and negate new x
// set the transform
ctx.setTransform(xAX, xAY, yAX, yAY, 0, 0);
}
Drawing an image
Lets create a function that will draw an image anywhere on the canvas that is rotated and scaled uniformly. We will use the center of the image as the reference point
function drawImageScaleRotate(img, x, y, scale, rotate) {
// define the direction and scale of x axis
const xAX = Math.cos(rotate) * scale;
const xAY = Math.sin(rotate) * scale;
// create the transform with yaxis at 90 CW of x axis and origin at x, y
ctx.setTransform(xAX, xAY, -xAY, xAX, x, y);
// Draw the image so that its center is at the new origin x, y
ctx.drawImage(img, -img.width / 2, -img.height / 2);
}
There is much more
When we set the transform with ctx.setTranform we replace the existing transform. This transform remains current. If we use ctx.transform, ctx.rotate, ctx.scale, ctx.translate the transforms are applied to the current transform, you build a transform in stages.
The transform functions are relatively expensive in terms of CPU cycles. That is way using sin and cos to build the matrix is much faster than using ctx.scale, ctx.rotate, ctx.translate to do the same thing starting from default.
Building transforms can become tricky as we need to keep track of what stage we are at.
We generally only use these function not to transform a single image (text, path, or what ever) but to create linked transforms.
For example a game object like a tank. The body of the tank is transformed (rotated and positioned) then the turret which is rotated with the body but has an additional independent rotation by using ctx.rotate. Full explanation is beyond the scope of this question.
The final function
From all this we can create a simplified function that will draw an image with its center at any location, that is uniformly scaled and rotated
function drawImageScaleRotate(img, x, y, scale, rotate) {
const xAX = Math.cos(rotate) * scale;
const xAY = Math.sin(rotate) * scale;
ctx.setTransform(xAX, xAY, -xAY, xAX, x, y);
ctx.drawImage(img, -img.width / 2, -img.height / 2);
}
To reset the transform to the default use ctx.resetTransform NOTE not fully supported yet or use ctx.setTransform(1,0,0,1,0,0);
Animated transforms faster than CSS + HTML or SVG
Using the above function is the 2nd fastest way to draw animated rotated scaled images, faster than CSS + HTML or SVG. You can literally fill the screen with animated images.
Demo
var w,h;
var image = new Image;
image.src = "https://i.stack.imgur.com/C7qq2.png?s=328&g=1";
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.style.position = "absolute";
canvas.style.top = "0px";
canvas.style.left = "0px";
document.body.appendChild(canvas);
const resize = () => { w = canvas.width = innerWidth; h = canvas.height = innerHeight;}
const rand = (min,max) => Math.random() * (max ?(max-min) : min) + (max ? min : 0);
const DO = (count,callback) => { while (count--) { callback(count) } }
resize();
addEventListener("resize",resize);
const sprites = [];
DO(500,()=>{
sprites.push({
xr : rand(w), yr : rand(h),
x : 0, y : 0, // actual position of sprite
r : rand(Math.PI * 2),
scale : rand(0.1,0.25),
dx : rand(-2,2), dy : rand(-2,2),
dr : rand(-0.2,0.2),
});
});
function drawImage(image, spr){
const xAX = Math.cos(spr.r) * spr.scale;
const xAY = Math.sin(spr.r) * spr.scale;
ctx.setTransform(xAX, xAY, -xAY, xAX, spr.x, spr.y);
ctx.drawImage(image, -image.width / 2, -image.height / 2);
}
function update(){
var ihM,iwM;
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,w,h);
if(image.complete){
var iw = image.width;
var ih = image.height;
for(var i = 0; i < sprites.length; i ++){
var spr = sprites[i];
spr.xr += spr.dx;
spr.yr += spr.dy;
spr.r += spr.dr;
// keeps images in canvas adds space to all sides so that image
// can move completely of the canvas befor warping to other side
// I do this to prevent images visualy popping in and out at edges
iwM = iw * spr.scale * 2 + w;
ihM = ih * spr.scale * 2 + h;
spr.x = ((spr.xr % iwM) + iwM) % iwM - iw * spr.scale;
spr.y = ((spr.yr % ihM) + ihM) % ihM - ih * spr.scale;
drawImage(image,spr);
}
}
requestAnimationFrame(update);
}
requestAnimationFrame(update);
If you are wondering which is the fastest way to draw animated content. That is via webGL. The above can draw 1000 scaled rotated images on most devices at a good frame rate. WebGL can easily draw 10000 (with extra features eg colored) in the same time.
You can rotate your image easily. You can specify angles by which you wants to apply rotation.
Source : https://www.w3schools.com/cssref/css3_pr_transform.asp
<style>
img.a {
transform: rotate(180deg);
}
</style>
<img class="a" src="https://picsum.photos/id/237/200/300"/>
use transform "rotate".and use it as below
p{
color:red;
font-size:12px;
text-align:center;
}
.rotate1{
transform:rotate(45deg);
margin-top:40px;
}
.rotate2{
transform:rotate(90deg);
margin-top:40px;
}
.rotate3{
transform:rotate(180deg);
margin-top:40px;
}
<p class="rotate1">ROTATE1</p>
<p class="rotate2">ROTATE2</p>
<p class="rotate3">ROTATE3</p>
You have ctx.rotate(radians) function.
Read below:
https://www.w3schools.com/tags/canvas_rotate.asp
I've created a basic HTML5 image slider where images move from top to bottom in a canvas.
I want all the images rotated at angle of 5 degrees. When I tried it out there seems to be some
distortion to the canvas and the image is not properly rotated.
I've tried the method for rotation mentioned in the below post
How do I rotate a single object on an html 5 canvas?
Fiddle - http://jsfiddle.net/DS2Sb/
Code
this.createImage = function (image, width, height) {
var fbWallImageCanvas = document.createElement('canvas');
var fbWallImageCanvasContext = fbWallImageCanvas.getContext('2d');
fbWallImageCanvas.width = width;
fbWallImageCanvas.height = height;
fbWallImageCanvasContext.save();
fbWallImageCanvasContext.globalAlpha = 0.7;
this.rotateImage(image, 0, 0, width, height, 5, fbWallImageCanvasContext);
fbWallImageCanvasContext.drawImage(image, width, height);
fbWallImageCanvasContext.restore();
return fbWallImageCanvas;
};
this.rotateImage = function (image, x, y, width, height, angle, context) {
var radian = angle * Math.PI / 180;
context.translate(x + width / 2, y + height / 2);
context.rotate(radian);
context.drawImage(image, width / 2 * (-1), height / 2 * (-1), width, height);
context.rotate(radian * (-1));
context.translate((x + width / 2) * (-1), (y + height / 2) * (-1));
};
The distortion you see is due to the fact that a rotated image will only fit in a larger canvas. So what we see is a rectangle view on a rotated image.
The computations are not that easy to get things done properly, but instead of pre-computing the rotated image, you might rotate them just when you draw them, which lets you also change the angle whenever you want (and opacity also btw).
So i simplified createImage, so that it just stores the image in a canvas (drawing a canvas is faster than drawing an image) :
this.createImage = function(image , width, height) {
var fbWallImageCanvas = document.createElement('canvas');
fbWallImageCanvas.width = width;
fbWallImageCanvas.height = height;
var fbWallImageCanvasContext = fbWallImageCanvas.getContext('2d');
fbWallImageCanvasContext.drawImage(image,0,0);
return fbWallImageCanvas;
};
And i changed drawItem so it draws the image rotated :
this.drawItem = function(ct) {
var angle = 5;
var radian = angle * Math.PI/180;
ct.save();
ct.translate(this.x + this.width/2 , this.y + this.height/2);
ct.rotate(radian);
ct.globalAlpha = 0.7;
ct.drawImage(fbc, - this.width/2, -this.height/2 , this.width, this.height);
ct.restore();
this.animate();
};
You'll probably want to refactor this, but you see the idea.
fiddle is here :
http://jsfiddle.net/DS2Sb/1/
Here is a link to a small html5 tutorial I created a while ago:
https://bitbucket.org/Garlov/html5-sidescroller-game-source
And here is the rotate code:
// save old coordinate system
ctx.save();
// move to the middle of where we want to draw our image
ctx.translate(canvas.width/2, canvas.height-64);
// rotate around that point
ctx.rotate(0.02 * (playerPosition.x));
//draw playerImage
ctx.drawImage(playerImage, -playerImage.width/2, -playerImage.height/2);
//and restore coordniate system to default
ctx.restore();
I am experimenting with animation in <canvas> and can't work out how to draw an image at an angle. The desired effect is a few images drawn as usual, with one image rotating slowly. (This image is not at the centre of the screen, if that makes any difference).
You need to modify the transformation matrix before drawing the image that you want rotated.
Assume image points to an HTMLImageElement object.
var x = canvas.width / 2;
var y = canvas.height / 2;
var width = image.width;
var height = image.height;
context.translate(x, y);
context.rotate(angleInRadians);
context.drawImage(image, -width / 2, -height / 2, width, height);
context.rotate(-angleInRadians);
context.translate(-x, -y);
The x, y coordinates is the center of the image on the canvas.
It is interesting that the first solution worked for so many people, it didn't give the result I needed.
In the end I had to do this:
ctx.save();
ctx.translate(positionX, positionY);
ctx.rotate(angle);
ctx.translate(-x,-y);
ctx.drawImage(image,0,0);
ctx.restore();
where (positionX, positionY) is the coordinates on the canvas that I want the image to be located at and (x, y) is the point on the image where I want the image to rotate.
I have written a function (based on Jakub's answer) that allows user to paint an image in a X,Y position based on a custom rotation in a custom rotation point:
function rotateAndPaintImage ( context, image, angleInRad , positionX, positionY, axisX, axisY ) {
context.translate( positionX, positionY );
context.rotate( angleInRad );
context.drawImage( image, -axisX, -axisY );
context.rotate( -angleInRad );
context.translate( -positionX, -positionY );
}
Then you can call it like this:
var TO_RADIANS = Math.PI/180;
ctx = document.getElementById("canvasDiv").getContext("2d");
var imgSprite = new Image();
imgSprite.src = "img/sprite.png";
// rotate 45º image "imgSprite", based on its rotation axis located at x=20,y=30 and draw it on context "ctx" of the canvas on coordinates x=200,y=100
rotateAndPaintImage ( ctx, imgSprite, 45*TO_RADIANS, 200, 100, 20, 30 );