Transforms in Pycairo - translate

By Martin McBride, 2019-09-27
Tags: transform translation user space device space
Categories: pycairo


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:

Translation

The 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 save and 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()

We call 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.

See also

If you found this article useful, you might be interested in the book NumPy Recipes or other books by the same author.

Join the PythonInformer Newsletter

Sign up using this form to receive an email when new content is added:

Popular tags

2d arrays abstract data type alignment and angle animation arc array arrays bar chart bar style behavioural pattern bezier curve built-in function callable object chain circle classes clipping close closure cmyk colour combinations comparison operator comprehension context context manager conversion count creational pattern data science data types decorator design pattern device space dictionary drawing duck typing efficiency ellipse else encryption enumerate fill filter font font style for loop formula function function composition function plot functools game development generativepy tutorial generator geometry gif global variable gradient greyscale higher order function hsl html image image processing imagesurface immutable object in operator index inner function input installing iter iterable iterator itertools join l system lambda function latex len lerp line line plot line style linear gradient linspace list list comprehension logical operator lru_cache magic method mandelbrot mandelbrot set map marker style matplotlib monad mutability named parameter numeric python numpy object open operator optimisation optional parameter or pandas partial application path pattern permutations pie chart pil pillow polygon pong positional parameter print product programming paradigms programming techniques pure function python standard library radial gradient range recipes rectangle recursion reduce regular polygon repeat rgb rotation roundrect scaling scatter plot scipy sector segment sequence setup shape singleton slice slicing sound spirograph sprite square str stream string stroke structural pattern subpath symmetric encryption template tex text text metrics tinkerbell fractal transform translation transparency triangle truthy value tuple turtle unpacking user space vectorisation webserver website while loop zip zip_longest