I want to draw a semi-complex element on a canvas rotated without rotating the canvas so that I don't need to calculate all of the various x/y points of the element.
I think that the basic process I need to use is:
translate the 0,0 point to the spot the drawn element will be rotated around,
rotate the canvas,
draw the element,
rotate the canvas back,
restore the origin point.
I will need to do this more than once. I've read that the rotate / rotate back part can introduce some error, with the final image being off just a bit. Is there a way to avoid this?
Before you perform the rotation and translation, call context.save(). This will create a snapshot of the current transformation of the canvas (as well as some other things, like drawing style, clip region, etc., but not the pixel data) and store it on a stack.
After you drew the shape, call context.restore(). This will pop the last saved state from the state stack and restore the current drawing state of the canvas to it.
You can do this as often as you want without accumulating any rounding differences.
Example function:
function drawImageRotated(x, y, rotation, image) {
context.save();
context.translate(x, y);
context.rotate(rotation);
context.drawImage(image, -image.width / 2, -image.height / 2);
context.restore();
// context translation and rotation are now on the same state they were
// before the function call
}
For more information about the canvas state, refer to the canvas specification.
Related
I have few coordinates in an XML file. They are lines, circles and arcs. I am reading them in a data structure and then trying to plot them on a canvas. What i am trying to figure out is how to divide the canvas into sub canvases. e.g suppose my canvas is
<canvas id="myCanvas" width="800" height="600" role="img">
Your browser does not support the canvas element.
</canvas>
What I am trying to achieve is how to make an imaginary window of width and height of 200px starting from say x1=200px on canvas and y1=250. And draw the image I have only in that box.
I have managed to scale down the image based on the imaginary box but cannot get around the concept of how to draw only in that imaginary box. The points are randomly distributed.
There are other ways to achieve this but the one you'll probably find most useful in this context is to use translation and a clip mask:
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
/// for simplicity, save current settings
ctx.save();
/// move coordinate system to the upper left corner of isolated region
ctx.translate(offsetX, offsetY);
/// create a clipping mask by using a simple rectangle
ctx.beginPath();
ctx.rect(0, 0, width, height);
/// define the last path (rectangle) as clipping mask
ctx.clip();
/// ... draw other things into this region from offset 0...
ctx.restore(); /// done and back to full canvas
By moving the whole coordinate system to the upper left corner of your region you can use offsets relative to the new isolated area. By adding a clip mask anything drawn outside the region will be clipped.
You will need to do this for each region one by one.
Another way is to add an offset to all drawing points. For example:
ctx.lineTo(x + offsetX, y + offsetY);
where offsetX/Y is the upper left corner of the region.
However, it will get more complicated if you need clipping - not a huge issue with images as you can define the destination region but for lines and and other path operation you will need to clip yourself by using interpolation etc.
Here is a live demo demonstrating this:
Fiddle (updated link)
The demo sets up a canvas and context and then fills the whole with a red color.
Then if sets the clipping and mask and translate it.
We now have a "virtual canvas" and the other graphic is intact
We now fill the region with the same fill operation but with blue. Now we can see only this regions is filled even the size is outside the actual region
Then we remove the clip and draw a line as evidence that we are now back in full mode
I just wonder how do the Canvas transformations work. Lets say i have a canvas with a circle drawn somewhere inside of it, and i want to scale the circle, so its center point will not move.
So i thought about doing the following:
translate(-circle.x, -circle.y);
scale(factor,factor);
translate(circle.x,circle.y);
// Now, Draw the circle by calling arc() and fill()
Is it the right way to do it? I just don't understand whether the canvas was designed to remember the order that i call the transformations.
Thanks.
Yes, you are correct.
The canvas accumulates all transforms and applies them to any future drawing.
So if you scale 2X, your circle will be drawn at 2X…and(!) every draw after that will be 2X.
That’s where saving the context is useful.
If you want to scale your circle by 2X but then have every subsequent drawing be at normal 1X you can use this pattern.
// save the current 1X context
Context.save();
// move (translate) to where you want your circle’s center to be
Context.translate(50,50)
// scale the context
Context.scale(2,2);
// draw your circle
// note: since we’re already translated to your circles center, we draw at [0,0].
Context.arc(0,0,25,0,Math.PI*2,false);
// restore the context to it’s beginning state: 1X and not-translated
Context.restore();
After Context.restore, your translate and scale will not apply to further drawings.
Say I drew a rectangle on the canvas. Surely there is some sort of built in method to get the XY coordinates, and dimensions of that rectangle? But after some googling I came up with nothing. And just to clarify, I am not talking about the coordinates of the canvas element itself, but rather a shape/image that is drawn unto the canvas.
Any help is appreciated.
If you're talking about a 2D canvas drawing, then the drawing maps 1:1 with screen coordinates, so it is just location of <canvas> + location of the drawing.
To clarify, drawing on a <canvas> basically just changes the pixels of the canvas - after you draw to it, you can't reference the drawn object the same way you can reference an html element.
Canvas is 2D table (Array) of numbers (= pixels = colors). When drawing into canvas, you are just editing this table. When you draw into canvas (= change numbers in table), what should be the coordinates of your adjustment?
If you are drawing rectangles only and you can define the coordinates for your rectangle, you must know your coordinates inside a program, because you have just drawn it.
If you want your image to be separated into some "objects" (shapes), you should use SVG.
Basically, you should be using canvas as a means to output graphics to the screen and the rest of your logic goes straight into the JavaScript that powers your game/application. The best way to go about making something like this is to create objects and assign properties to them; in its simplest form that can look like this:
function Player(x, y)
{
this.x = x;
this.y = y;
}
var examplePlayerObject = new Player(20, 20);
By extending this object via prototyping you can create multiple copies of an object that has the exact same functions; such as draw. Drawing the player in this instance could just be a red square that is 20px*20px.
Player.prototype.draw = function()
{
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = 'red';
context.fillRect(this.x, this.y, 20, 20);
}
Then, you should have an update step with some means of clearing what is on the screen and redrawing the parts which have changed.
function animationStep()
{
examplePlayerObject.x++;
examplePlayerObject.y++;
examplePlayerObject.draw();
}
This animation step should run each frame; look into requestAnimationFrame for smooth animation. Paul Irish has a good shim for older browsers. Add in requestAnimationFrame(animationStep) at the end of that function and you will have a red square moving slowly across the screen. Good luck!
I wanted to know if Html5 supports shape translation in canvas..For instance I have a rectangle,is it possible to apply a transformation to it?
canvas = document.getElementById('Canvas');
context =canvas.getContext('2d');
context.rect(myRectangle.x,myRectangle.y,myRectangle.width,myRectangle.height);
There's a few different methods for animating and changing the position that you want to draw your thingy. Either way, if you're after an animation, you're going to need to clear your canvas and keep drawing - like a flip book if you will.
Choices for setting the newly drawn item include:
moveTo - to move to the new position of your thing
translate - to translate the centre point of the canvas and keep the drawing positions the same, but move the underlying coordinate system
.rect(newX, newY, height, width) - drawing the specific position
I mocked together a (contrived) example of using translate on a canvas - which will move the the animating box around the position of your cursor. It's done in a loop - and I'd suggest checking out Paul Irish's article on requestAnimFrame for better animation loops. Here's the example: http://jsbin.com/afofur/2/edit#preview
As the comments say in the previous answer - SVG maintains a object model, so you can reference objects on the page, canvas is a bitmap API (basically), and once the pixels are committed to the canvas, there's no reference to the method or shape behind the drawing, it's just pixels to the canvas API.
No, once it is drawn to the canvas you can't change it anymore, there is no in-memory representation of the shapes you draw on the canvas. However, you can transform the canvas before you draw the shape and reset transform (canvas.setTransform(1, 0, 0, 1, 0, 0)) after you've drawn the shape.
Edit
Remember that the canvas API doesn't keep track of which objects you draw. It just fills the pixels with a color where you ask it to draw a rectangle. If you want to make animations, you will have to keep track of which rectangle you drawn yourself (make an object with properties x, y, width, height). Then you will have to do the following in each animation step:
clear the canvas
update the objects for the new time frame
redraw the canvas
You can find a tutorial here.
I'm drawing a symbol with simple lines, but want the user to be able to specify the rotation (in 90degrees only).
Which also means that the dimensions of my canvas change.
No I calculate the dimensions, and set the canvas size. Then I set the center of the rotation to the center of my canvas (via ctx.translate) and rotate to the arbitrary degrees.
Now my problem is: How do I set the translation back to the upper left corner, so I can draw my symbol normally from that position? Do I really have to calculate the values with the rotation?
Thanks.
Great question! translate rotate and scale are all functions that operate upon the current matrix. This gives you lots of options. Probably the simplest thing is to do a save restore on the context matrix
ctx.save();
ctx.translate ( to the center );
ctx.rotate ( do rotation );
//Draw rotated stuff
ctx.restore();
//Draw non-rotated stuff
Now after you call restore, the matrix reverts to how it was before the last save - In opengl, this is actually a stack, and you can push many contexts, but I'm not sure if webgl supports that.
This link may also be helpful: https://developer.mozilla.org/en/drawing_graphics_with_canvas
Hope this helps.
Update:
Yes. Okay, so you are misunderstanding something a little bit. The translations and rotations are applied before the drawing. This is because of a lot of complex math, and is really beyond the scope of this question. So if you want to draw part of your canvas rotated one way, and the other part of it rotated a different way, you first save, then apply the transformations, do the first part of the drawing, then restore to get back to the pre-transformed state. At which point, you can repeat.
So, for example, you can do this:
ctx.save();
ctx.translate ( x_center, y_center );
ctx.rotate ( 90 );
//Draw your rotated stuff starting at the center
ctx.translate ( -x_center, -y_center );
//Draw your main frame stuff that is all rotated around the center
ctx.restore();
ctx.save();
ctx.rotate ( 90 );
//Draw your text which is rotated around the top-left corner
ctx.restore();
In this way, you have 1 drawing function, and you simply setup a context before you draw the different components.