Transforms in Pycairo - translate
Martin McBride, 2019-09-27
Tags transform translation user space device space
As we have seen in earlier examples, Pycairo uses a coordinate system to create images. In this section we will look at how we can use transforms to make this system more flexible.
We will see how to apply:
- Translation, to move items around the page (this article).
- Scaling, to change the size of items.
- Rotation, to change the orientation of items.
User space and device space
Pycairo actually has two separate coordinate spaces user space and device space. You do not usually need to think about them separately, because by default they are identical.
Here is what user space and device space look like when we are creating a 600 by 400 pixel image:
Device space is a fixed space that represents the output image. The space is 600 by 400, and represents the actual pixels in the output image.
User space is a the space where you actually define the shapes you want to draw. User space isn't measured in pixels, it is measured in units, where a unit can be changed to be whatever you like. When Pycairo draws a shape, it maps the coordinates from user space to device space, to calculate where to place the shape.
With the default mapping, this is a very simple calculation. For example, this code defines a rectangle in user space at (100, 100). The rectangle is 100 units wide and 200 units high:
ctx.rectangle(100, 100, 100, 200) ctx.set_source_rgb(0, 0, 1) ctx.fill()
This defines the rectangle in user space. Pycairo then draws the rectangle in device space. Because of the default mapping, the rectangle is drawn in exactly the same size and place in device space:
translate function moves the position of user space relative to device space. For example, we can call
translate like this:
ctx.translate(200, 100) ctx.rectangle(100, 100, 100, 200) ctx.set_source_rgb(0, 0, 1) ctx.fill()
User space is shifted 200 units to the right and 100 units down. It will look like this:
As before, we have drawn our rectangle at location (100, 100). But remember that the coordinates are specified in user space, then transformed into device space to actually draw on the page. Location (100, 100) in user space maps onto (300, 200) in device space due to the translation.
Notice that device space is unchanged - device space never changes, it represents the pixels of the final image file.
Benefits of transforms
Of course, instead of using
translate we could simply have modified our code to draw the rectangle in a different place:
ctx.rectangle(300, 200, 100, 200) ctx.set_source_rgb(0, 0, 1) ctx.fill()
This would have the same effect.
However, it is often easier to use translation because it simplifies your code. For example, if we had code to draw a complex shape that we wanted to duplicate in different places on the page, we would need to write our drawing code to offset all parts of the shape when we wanted to draw it in a different place. But if we use transforms, we just need a single call to
translate and our original drawing code will automatically draw the shape in a different place.
Transforms can also be used to scale or rotate objects, or even perform more complex transformations, which would be very difficult to implement without transforms.
Undoing a transform
What if we have drawn a particular shape using a translation, but then we want to draw some more shapes using the default translation? There are several ways to do this. The most obvious is to apply the reverse translation:
ctx.translate(200, 100) ctx.rectangle(100, 100, 100, 200) ctx.set_source_rgb(0, 0, 1) ctx.fill() ctx.translate(-200, -100)
This code draws the rectangle with the translation (200, 100) applied. It then translates user space through (-200, -100), which puts it back in its original position, so any further drawing will take place in the original coordinates. This works, but it can get a bit complicated if you have complex transformations.
A better way is to use
restore. Here is how it works:
ctx.save() ctx.translate(200, 100) ctx.rectangle(100, 100, 100, 200) ctx.set_source_rgb(0, 0, 1) ctx.fill() ctx.restore()
save before the initial
translate. This saves the state of the context, which includes any transformations currently applied.
When we have finished drawing, we call
restore, which sets the context back to the state it was in when
save was first called. Any transformations will be undone.