using HTML5 Canvas - rotate image about arbitrary point - html

Rotate the dial on top of a semi circular(Northern Hemisphere) image as background.
range could be 0 - 180 degrees.
on input to the method that does canvas transformation, the dial would rotate and stop over the matched value.
Here's what I was trying based on help and sample passed on by phrogz

In general, what you want to do is:
Transform the context to the point on the canvas that the object should rotate about.
Rotate the context.
Transform the context by the negative offset within the object for the center of rotation.
Draw the object at 0,0.
In code:
ctx.save();
ctx.translate( canvasRotationCenterX, canvasRotationCenterY );
ctx.rotate( rotationAmountInRadians );
ctx.translate( -objectRotationCenterX, -objectRotationCenterY );
ctx.drawImage( myImageOrCanvas, 0, 0 );
ctx.restore();
Here's a working example showing this in action. (The math for the rotation was just experimentally hacked to find a swing amount and offset in radians that fit the quickly-sketched gauge dial.)
As may be evident, you can substitute the translate call in step #3 above with offsets to the drawImage() call. For example:
ctx.drawImage( myImageOrCanvas, -objectRotationCenterX, -objectRotationCenterY );
Using context translation is recommended when you have multiple objects to draw at the same location.

Related

Scale, Position & Rotate Parent object to make child object take up entire stage

Using the first photo below, let's say:
The red outline is the stage bounds
The gray box is a Sprite on the stage.
The green box is a child of the gray box and has a rotation set.
both display object are anchored at the top-left corner (0,0).
I'd like to rotate, scale, and position the gray box, so the green box fills the stage bounds (the green box and stage have the same aspect ratio).
I can negate the rotation easily enough
parent.rotation = -child.rotation
But the scale and position are proving tricky (because of the rotation). I could use some assistance with the Math involved to calculate the scale and position.
This is what I had tried but didn't produce the results I expected:
gray.scaleX = stage.stageWidth / green.width;
gray.scaleY = gray.scaleX;
gray.x = -green.x;
gray.y = -green.y;
gray.rotation = -green.rotation;
I'm not terribly experienced with Transformation matrices but assume I will need to go that route.
Here is an .fla sample what I'm working with:
SampleFile
You can use this answer: https://stackoverflow.com/a/15789937/1627055 to get some basics. First, you are in need to rotate around the top left corner of the green rectangle, so you use green.x and green.y as center point coordinates. But in between you also need to scale the gray rectangle so that the green rectangle's dimensions get equal to stage. With uniform scaling you don't have to worry about distortion, because if a gray rectangle is scaled uniformly, then a green rectangle will remain a rectangle. If the green rectangle's aspect ratio will be different than what you want it to be, you'd better scale the green rectangle prior to performing this trick. So, you need to first transpose the matrix to offset the center point, then you need to add rotation and scale, then you need to transpose it away. Try this set of code:
var green:Sprite; // your green rect. The code is executed within gray rect
var gr:Number=green.rotation*Math.PI/180; // radians
var gs:Number=stage.stageWidth/green.width; // get scale ratio
var alreadyTurned:Boolean; // if we have already applied the rotation+scale
function turn():void {
if (alreadyTurned) return;
var mat:flash.geom.Matrix=this.transform.matrix;
mat.scale(gs,gs);
mat.translate(-gs*green.x,-gs*green.y);
mat.rotate(-1*gr);
this.transform.matrix=mat;
alreadyTurned=true;
}
Sorry, didn't have time to test, so errors might exist. If yes, try swapping scale, translate and rotate, you pretty much need this set of operations to make it work.
For posterity, here is what I ended up using. I create a sprite/movieClip inside the child (green) box and gave it an instance name of "innerObj" (making it the actually content).
var tmpRectangle:Rectangle = new Rectangle(greenChild.x, greenChild.y, greenChild.innerObj.width * greenChild.scaleX, greenChild.innerObj.height * greenChild.scaleY);
//temporary reset
grayParent.transform.matrix = new Matrix();
var gs:Number=stage.stageHeight/(tmpRectangle.height); // get scale ratio
var mat:Matrix=grayParent.transform.matrix;
mat.scale(gs,gs);
mat.translate(-gs * tmpRectangle.x, -gs * tmpRectangle.y);
mat.rotate( -greenChild.rotation * Math.PI / 180);
grayParent.transform.matrix = mat;
If the registration point of the green box is at one of it's corners (let's say top left), and in order to be displayed this way it has a rotation increased, then the solution is very simple: apply this rotation with negative sign to the parent (if it's 56, add -56 to parent's). This way the child will be with rotation 0 and parent -> -56;
But if there is no rotation applied to the green box, there is almost no solution to your problem, because of wrong registration point. There is no true way to actually determine if the box is somehow rotated or not. And this is why - imagine you have rotated the green box at 90 degrees, but changed it's registration point and thus it has no property for rotation. How could the script understand that this is not it's normal position, but it's flipped? Even if you get the bounds, you will see that it's a regular rectangle, but nobody know which side is it's regular positioned one.
So the short answer is - make the registration point properly, and use rotation in order to display it like in the first image. Then add negative rotation to the parent, and its all good :)
Edit:
I'm uploading an image so I can explain my idea better:
 
As you can see, I've created a green object inside the grey one, and the graphics INSIDE are rotated. The green object itself, has rotation of 0, and origin point - top left.
#Vesper - I don't think that the matrix will fix anything in this situation (remember that the green object has rotation of 0).
Otherwise I agree, that the matrix will do a pretty job, but there are many ways to do it :)

HTML5 Canvas - Zooming into a Point

So I know there are threads about it already here, like that one.
I followed the idea that was proposed in the thread above, and it works. However, I don't understand WHY it works.
Here is an example:
Let's say that i have a square centered at (100, 100), and its width/height is 100. So its top-left corner will be at (50, 50).
Now let's say that i want to zoom X2 into the square's center, that is, to zoom into (100, 100). So i will write the following transformation sequence:
translate(100, 100);
scale(2, 2);
translate(-100, -100);
So because the canvas apply the transformation in reverse order, my transformed square's top-left corner will be now at (0, 0), and its height/width will be 200.
Ok, let's say that now i want to zoom X2 into the right-bottom corner of the already transformed square. So intuitively, i would like to perform the following transformation sequence:
translate(200, 200);
scale(2, 2);
translate(-200, -200);
But it wont work, because again, the canvas apply transfomations in reverse order. That is to say, that if i sum up my two transformation sequences, i'll get:
// First Sequence
translate(100, 100);
scale(2, 2);
translate(-100, -100);
// Second Sequence
translate(200, 200);
scale(2, 2);
translate(-200, -200);
This means that the second sequence will be applied to each point before the first sequence (because the canvas will apply the transformation from bottom to top), and this is wrong. So as the thread in the link above suggest the following:
Because sequence 2 will be applied first, i should transform the point (200, 200) to its original coordinates, by applying to it the inverse of the first sequence. that is to say, if T1 is the matrix that represents the first sequence, then it will look like that:
// First Sequence
translate(100, 100);
scale(2, 2);
translate(-100, -100);
// Second Sequence
var point = SVGPoint(200, 200);
var transformedPoint = point.matrixTransform(T1.inverse());
translate(-transformedPoint.x, -transformedPoint.y);
scale(2, 2);
translate(transformedPoint.x, transformedPoint.y);
But why it works? I really don't understand why it should work like that... can anyone elaborate about it?
Thanks!
The HTML5 canvas transformations happen top-down, not bottom-up as you believe. The reason for the distinction is because the transformations applied to the canvas affect the coordinate system, not your logical coordinates.
Translating by translate(100, 100) will move your coordinate system right and down, which appears hauntingly similar to moving your logical coordinate up and left.
Let's take the first sequence (I have changed your use of transform to translate):
translate(100, 100);
scale(2, 2);
translate(-100, -100);
Naturally, when we think to scale an object from it's center, we translate the object to (0,0), scale the object, then move the object back. The above code, when read in reverse, would appear to do that. However, that's not the case.
When we read the above code from top-down, it says (assume we start with an identity transform):
Move the context's (0,0) right 100 units and down 100 units. This takes it to the canvas's (100,100) location.
Make the coordinate system 2x bigger.
Move the context's (0,0) left 100 units and up 100 units, essentially returning it to it's original location (in context coordinate space, not canvas space).
The scaling happens relative to the context's (0,0) point, which is at (100,100) on the canvas.
If we were to now add your second sequence:
translate(200, 200);
scale(2, 2);
translate(-200, -200);
This will:
Move the context's (0,0) to the coordinate system's (200,200) location.
Make the coordinate system 2x bigger than it already was.
Return the context's (0,0) back to where it was previously (in context coordinate space, not canvas space).
As you've found out, that does not give you what you are expecting because (200,200) is not the point about which you want to scale. Remember, all units are relative to the context coordinate system. So we need to convert the canvas location of (200,200) to the context coordinate location of (150,150) which is the original bottom-right corner of our rectangle.
So we change sequence #2 to be:
translate(150, 150);
scale(2, 2);
translate(-150, -150);
This gives us what we are expecting (to zoom in on the bottom-right corner of the rectangle). That's why we do the inverse-transform.
In the demo application, when the app zoom's in, it's taking the coordinate in canvas units where the user's mouse was, inverse-transforming that using the context transformation thus-far, to get the location in context coordinate space that was clicked on. The context origin is moved to that location, zoomed, then returned to it's previous location.
References:
Safari HTML5 Canvas Guide: Translation, Rotation, and Scaling
You seem to be way overthinking transforms!
Here’s the simple rule:
If you apply any set of transforms, then you must undo all of them if you want to get back to your untransformed state.
Period !!!!
So let say you do these 4 transforms:
Do #1. context.translate(100,100);
Do #2. context.scale(2,2);
Do #3. context.translate(-20,50);
Do #4. context.scale(10,10);
To get back to your original untransformed state, you must undo in exactly reverse order:
Undo #4: context.scale( 0.10, 0.10 ); // since we scaled 10x, we must unscale by 0.10
Undo #3: context.translate(20,-50);
Undo #2: context.scale( 0.50, 0.50 ); // since we scaled 2x, we must unscale by 0.50
Undo #1: context.translate(-100,-100);
Think of it like walking to your friends house.
You turn Right + Left + Right.
Then to go home you must reverse that: Left + Right + Left
You must undo your walking path in exactly the reverse of your original walk.
That’s how transforms work too.
That’s Why !!

Rotate the group and scribbling on the group in a stage.. postting with code

Please this fiddle I have copied my complete project in it
here if you open the fiddle in the output you can see an image, scribble on the image selecting pen,add text etc(like this perform some functions).then rotate the group using rotate button and then try to scribble you will understand what is the problem exactly.
In me view Problem is I am having a stage and a layer is added to the stage and a group is added to the layer and different elements like lines text etc are added to the group. then group is rotated the i am trying to draw the line based on the mouse position of the stage.But it is not coming correctly because the group got rotated the x and y what we are taking to draw a line is from stage.I need to take the x and y from the group not from the stage is their any way.If hav't understand please ask me or post a reply.
This should get you fairly close: http://jsfiddle.net/k4qB8/24/
// This rotates that added active line along with your group.
// This makes the draw direction correct
activeline.setRotationDeg(0-rootGroup.getRotationDeg());
// Here you'll have to figure a way to calculate how much to move the
// line over so the draw is on the correct spot
// This is as close as I got it
if(Math.abs(rootGroup.getRotationDeg()%360)==0)
activeline.move(rootGroup.getX()-375, rootGroup.getY()-175);
if(Math.abs(rootGroup.getRotationDeg()%360)==90)
activeline.move(rootGroup.getX()-175, rootGroup.getY()+375);
if(Math.abs(rootGroup.getRotationDeg()%360)==180)
activeline.move(rootGroup.getX()+375, rootGroup.getY()+175);
if(Math.abs(rootGroup.getRotationDeg()%360)==270)
activeline.move(rootGroup.getX()+175, rootGroup.getY()-375);
Also, add some more logic for counter-clockwise rotation, as this doesn't work 100%.
I think the real solution would be to just draw on separate layers for each rotation, kind of like this:
if (rotation is 90) : draw on lineLayer1;
if (rotation is 180) : draw on lineLayer2;
if (rotation is 270) : draw on lineLayer3;
if (rotation is 360 || 0) : draw on lineLayer4;
This way you could just rotate the layers which are not drawn on to simulate the feel of rotation.

How to use radian values in the Canvas method createRadialGradient()

Could someone give me an explanation of the 3rd & 6th params of the following Canvas method:
createRadialGradient(0, 0, 50, 0, 0, 100);
I've been told they're radian values and I'm not familiar with those types and would like to understand how to use them properly.
Thanks.
They're not radian values but radius values. See e.g. here for parameter descriptions and an example that you can run in your browser.
The 3rd param is the radius of the start circle, and the 6th param is the radius of the end circle. Visualize a circle moving from an initial center and radius, to a final center and radius, smoothly interpolating in between. So you get a cylinder or cone of color (or some shape in between cylinder and cone). If the two centers are the same, you get a disc or donut shape.
Play with the params in the example above and you'll see how the parts of the gradient change.
Increase the 3rd param, and you'll have a (bigger) "hole" in the middle of your gradient. Decrease the 6th param, and your gradient won't go out as far.

understanding matrix.transition(); as3

I am trying to understand the method transition that falls in the Matrix Class. I am using it to copy pieces of a bitMapData. But I need to better understand what transitions do.
I have a tilesheet that has 3 images on it. all 30x30 pixels. the width of the total bitmap is 90pxs.
The first tile is green, the second is brown, and the third is yellow. If I move over 30pxs using the matrix that transitions, instead of getting brown, I get yellow, if I move over 60px, I get brown.
If I move -30 pixels, then the order is correct. I am confused on what is going on.
tileNum -= (tileNumber * tWidth);
theMatrix = new Matrix();
theMatrix.translate(tileNum,0);
this.graphics.beginBitmapFill(tileImage,theMatrix);
this.graphics.drawRect(0, 0,tWidth ,tHeight );
this.graphics.endFill();
Can someone tell me how transitions work, or some resources that show how they work. I ultimately want to know a good way to switch back and forth between each tile.
First of all, don't confuse translation with transition. The latter is a general English word for "change", whereas to translate in geometry and general math is to "move" or "offset" something.
A transformation matrix defines how to transform, i.e. scale, rotate and translate, an object, usually in a visual manner. By applying a transformation matrix to an object, all pixels of that object are rotated, moved and scaled/interpolated according to the values stored inside the matrix. If you'd rather not think about matrix math, just think of the matrix as a black box which contains a sequence of rotation, scaling, and translation commands.
The translate() method simply offsets the bitmap that you are about to draw a number of pixels in the X and Y dimensions. If you use the default ("identity") matrix, which contains no translation, the top left corner of your object/bitmap will be in the (0,0) position, known as the origin or registration point.
Consider the following matrix:
var mtx : Matrix = new Matrix; // No translation, no scale, no rotation
mtx.translate(100, 0); // translated 100px on X axis
If you use the above matrix with a BitmapData.draw() or Graphics.beginBitmapFill(), that means that the top left corner of the original bitmap should be at (x=100; y=0) in the target coordinate system. Sticking to your Graphics example, lets first consider drawing a rectangle without a matrix transformation.
var shape : Shape = new Shape;
shape.graphics.beginBitmapFill(myBitmap);
shape.graphics.drawRect(0, 0, 200, 200);
This will draw a 200x200 pixels rectangle. Since there is no transformation involved in the drawing method (we're not supplying a transformation matrix), the top left corner of the bitmap is in (x=0; y=0) of the shape coordinate system, i.e. aligned with the top left corner of the rectangle.
Lets look at a similar example using the matrix.
var shape : Shape = new Shape;
shape.graphics.beginBitmapFill(myBitmap, mtx);
shape.graphics.drawRect(0, 0, 200, 200);
This again draws a rectangle that is 200px wide and 200px high. But where inside this rectangle will the top left corner of myBitmap be? The answer is at (x=100, y=0) of the shape coordinate system. This is because the matrix defines such a translation.
But what then will be to the left of (x=100; y=0)? With the above code, the answer is that the bitmap repeats to fill the entire rectangle, and hence you will see the rightmost side of the bitmap, to the left of the leftmost side, as if there was another instance of the bitmap right next to it. If you want to disable the repeating image, set the third attribute of beginBitmapFill() to false:
shape.graphics.beginBitmpFill(myBitmap, mtx, false);
Lets take a look at one last example that might help your understanding. Remember that the translation matrix defines the position of the top left corner of an image, in the coordinate system of the shape. With this in mind, consider the following code, using the same matrix as before.
var shape : Shape = new Shape;
shape.graphics.beginBitmapFill(myBitmap, mtx);
shape.graphics.drawRect(100, 0, 100, 100);
Notice that this will draw the rectangle 100px in on the X axis. Not coincidentally, this is the same translation that we defined in our matrix, and hence the position of the top left corner of the bitmap. So even though repeating is enabled, we will not see a repeating image to the left of our rectangle, because we only start drawing at the point where the bitmap starts.
So the bottom line is, I guess, that you could think of the transform matrix as a series of transformation commands that you apply to your image as you draw it. This will offset, scale and rotate the image as it's drawn.
If you are curious about the inner workings of the matrix, Google transformation matrices, or read up on Linear Algebra!