Scale rotate translate - Transform the user space
The codebase we have now allows us to draw any grayscale picture. We can freely define paths of any complexity and either stroke the line of the path or fill it with any grayscale tone. We are drawing with numbers whatever the numbers be. We have seen also that we have the full power of a program to define this numbers, using the math operators to interpolate, extrapolate, to express angles.
But what if we we want to reuse the same form at different places and at different sizes? We will have to do our math ourselves. Let's take our previos code for the letters T A O. We can set the position, but not change the size of the letters.
If we want to change the size, we will have to add a size parameter to the letter operators and apply that to each coordinate. It is possible, it makes the code more complex.
Now imagine you want to rotate the letters, too. We would need to write operators that makes the transformation for us, to keep it simple. These operators exist, it's scale, rotate and translate, which work on the transformation matrix.
What is the transformation matrix? First of all, it is an array of 6 with the initial values m = [1, 0, 0, 1, 0, 0]
We add an indirection: Each time, we create a path, we do not use the immediate values, but apply the transform function which is[x, y] = [ x * m[0] + y * m[2] + m[4],
.
x * m[1] + y * m[3] + m[5] ]
We have so the user space, that is the space that we give the coordinates in PostScript and the physical space we use to draw the path. This transformation happens immediately when we create the path. The context.path holds the coordinates in physical space.
We have operators to manipulate the transformation matrix.
Scale has an sx and sy parameter and works on indexes 0-3m = [ m[0]*sx,
m[1]*sy,
m[2]*sx,
m[3]*sy, m[4], m[5] ]
Rotate has a more complex interaction on the same indexesm = [ cos(a)*m[0] - sin(a)*m[2],
cos(a)*m[1] - sin(a)*m[3],
sin(a)*m[0] + cos(a)*m[2],
sin(a)*m[1] + cos(a)*m[3], m[4], m[5] ]
Translate actos on 4 and 5, but uses all indexesm = [ m[0], m[1], m[2], m[3],
m[4] + x*m[0] + y*m[2],
m[5] + x*m[1] + y*m[3] ]
There is also an inverse transformation function itransform. We will need that function to return the currentpoint in user space.
det = m[0]*m[3] - m[1]*m[2]
[x, y] = [ (x*m[3] - y*m[2] +m[2]*m[5] - m[4]*m[3])/det,
(-x*m[1] + y*m[0] + m[4]*m[1] - m[0]*m[5])/det ]
So how does that look? We define a house which starts at the origin. We draw it once normally, once scaled, once rotated and once translated.
As you can see, the rotation is about the origin. If you combine transformation operators, the order matters, because the transformation affect multiple indices of the transformation matrix.
We will implement the code in the next chapter.