Understanding HTML 5 canvas scale and translate order
Asked Answered
D

3

15

I'm doing some graphing around a center X,Y of 0,0. When it's time to render, I reposition with translate, and then use scale to make the graph fill the canvas (ie scale everything by 50% for example).

I notice that it matters whether you call scale and then translate, or translate and then scale and I can't quite get my head around it. This is a problem since everything doesn't always fit, but my mental model isn't complete so having a hard time fixing it.

Can someone explain why the order of the scale and translate calls matter?

Discordance answered 4/7, 2012 at 16:18 Comment(0)
N
25

So let's draw a grid on a 300x300 canvas:

http://jsfiddle.net/simonsarris/4uaZy/

enter image description here

This will do. Nothing special. A red line denotes where the origin is located by running through (0,0) and extending very very far, so when we translate it we'll see something. The origin here is the top left corner, where the red lines meet at (0,0).

All of the translations below happen before we draw the grid, so we'll be moving the grid. This lets you see exactly what's happening to the orgiin.


So lets translate the canvas by 100,100, moving it down+right:

enter image description here

http://jsfiddle.net/simonsarris/4uaZy/1/

So we've translated the origin, which is where the red X is centered. The origin is now at 100,100.


Now we'll translate and then scale. Notice how the origin is in the same place as the last image, everything is just twice as large.

enter image description here

http://jsfiddle.net/simonsarris/4uaZy/2/

Boom. The orgin is still at 100,100. Everything is puffed up by 2 though. The origin changed, then everything gets puffed up in place.


Now lets look at them in reverse. This time we scale first, so everything is fat from the start:

enter image description here

http://jsfiddle.net/simonsarris/4uaZy/3/

Everything is puffed by 2. The origin is at 0,0, its starting point.


Now we do a scale and then a translate.

enter image description here

http://jsfiddle.net/simonsarris/4uaZy/4/

We're translating by 100,100 still, but the origin has moved by 200,200 in real pixels. Compare this to two images previous.

This is because everything that happens after a scale must be scaled, including additional transforms. So transforming by (100,100) on a scaled canvas leads to it moving by 200, 200.


The takeaway thing to remember here is that changing the transformation affects how things are drawn (or transformed!) from then on. If you scale x2, and then translate, the translation will be x2

If you want to see, mathematically, what is happening at each step I encourage you to take a look at the code here:

https://github.com/simonsarris/Canvas-tutorials/blob/master/transform.js

This mimics the entire transformation process done by canvas and lets you see how previous transforms modify those that come afterwards.

Nuncia answered 4/7, 2012 at 17:56 Comment(6)
Excellent explanation. Seems so obvious now :)Discordance
Well, something still isn't making sense. Look at this one: jsfiddle.net/sdJ7v/1 Trial and error helped me center the blue circle within the green. But given the coordinates, that offset doesn't make sense. Not sure how transform is being applied...Discordance
please see my comments here: jsfiddle.net/simonsarris/sdJ7v/2 Does that make sense?Nuncia
In other words, the transform permanently changed the origin, and the scale permanently applies to any new calculations, so -25, -25 becomes -50, -50 and since the origin is really at 200,200, it ends up being drawn at really 150, 150Nuncia
So canvas basically applies the transformations backwards. The "translate then scale" actually scales first then translates. The "scale then trasnalte" actually translates first then scales.Understate
@Calmarius: Yes, you can see it that way. I've expanded on this in my answer below.Magical
A
2

Scaling and rotation are done respect to the origin so if your transform has a translation, for example, then this will make the order important.

Heres a good read: Why Transformation Order Is Significant

Areca answered 4/7, 2012 at 16:26 Comment(2)
Whoops, I said transform when I meant translate. Fixed. I read the article, but still don't get it. What does it mean that "scaling is done with respect to the origin"? Why does it matter if I scale everything by 50% where the origin is?Discordance
@Discordance It matters because if things shrink or grow, they shrink or grow around a single point in the plane. If that point is centered on your canvas 50% scaling is like "unzoom". If it's at top-left then everything in the canvas will move 50% closer to that point. If it's out visible region, everything you're looking at may well move off-screen as it travels 50% closer to that point. To put it another way, only the origin pixel is purely scaled, while the rest shrink and translate relative to their starting position.Homogenesis
M
1

Building on Simon Sarris's excellent answer, I'd like to explain what I personally found most confusing, which is the example in the HTML spec:

The transformations must be performed in reverse order.

For instance, if a scale transformation that doubles the width is applied to the canvas, followed by a rotation transformation that rotates drawing operations by a quarter turn, and a rectangle twice as wide as it is tall is then drawn on the canvas, the actual result will be a square.

Start with a 40x20 rectangle:

enter image description here https://jsfiddle.net/mhsmith/p6nLo9d4/2/

Obviously, adding ctx.scale(2, 1) makes it 80x20.

enter image description here https://jsfiddle.net/mhsmith/p6nLo9d4/4/

You might think that adding a quarter turn rotation (ctx.rotate(Math.PI / 2)) would rotate the rectangle so it's 20x80. But actually, it turns it into a square!

enter image description here https://jsfiddle.net/mhsmith/p6nLo9d4/5/

This is because the rotation is affected by the scale. To visualize this, here's what the above 3 images would look like with a circle around the origin:

       

https://jsfiddle.net/mhsmith/p6nLo9d4/8/

The scale turns the circle into an ellipse, and the subsequent rotate goes around the edge of that ellipse, not around the original circle. So as the X axis rotates from pointing right to pointing down, it gets compressed by a factor of 2. And as the Y axis rotates from pointing down to pointing left, it gets expanded by a factor of 2. The end result is that the 80x20 rectangle became a 40x40 square.

There are two ways of thinking about a series of transformations:

  1. Imagine them happening to the axes, in the order they appear in the code, with each step being measured against the axes that came out of the previous step; OR
  2. Imagine them happening to the image, in the reverse of the order they appear in the code, with each step being measured against the original axes.

This answer, and Simon Sarris's answer, are both written in the first way. The HTML spec is written in the second way, which I think is much easier to visualize.

Magical answered 21/9, 2023 at 10:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.