Scripton / Docs

Transforms

Consider the following example:

from scripton.canvas import Canvas canvas = Canvas(width=200, height=200, fill='gray') canvas.draw_rect(x=10, y=10, width=50, height=50, fill='white')

This draws a white 50×50 square near the top-right corner of the canvas.

We now insert a scaling transform before the draw_rect call:

from scripton.canvas import Canvas canvas = Canvas(width=200, height=200, fill='gray') canvas.scale(x=2, y=2) canvas.draw_rect(x=10, y=10, width=50, height=50, fill='white')

The results in a square twice as big (100×100) being drawn. Also notice that the square's top-left corner is now located at (20, 20).

The canvas.scale call does not affect any rendering before its call. Instead, it effectively scales the space in which subsequent drawing calls operate.

from scripton.canvas import Canvas canvas = Canvas(width=200, height=200, fill='gray') # This square is before the scale transform and remains unaffected canvas.draw_rect(x=10, y=10, width=50, height=50, fill='gold') canvas.scale(x=2, y=2) # This square is drawn in the scaled space canvas.draw_rect(x=10, y=10, width=50, height=50, fill='white')

Transformation Matrices

In its most general form, the canvas supports arbitrary affine transforms that can be represented, in an augmented matrix form, as:

The utility transformation methods (like scale, rotate, translate) are all special cases of this. However, you can always directly specify a transformation matrix. In Python, you can represent these matrices as —

  • A nested list or tuple representing either a 2×3 matrix (the final row implicitly being 0 0 1) or a 3×3 matrix (the final row must be 0 0 1)

    # 2×3 form scale = [ [2, 0, 0], [0, 2, 0] ] # 3×3 form translate = [ [1, 0, 3], [0, 1, 4], [0, 0, 1] ]
  • A NumPy 2×3 or 3×3 array

    identity = np.eye(3)

Composition

By default, canvas transforms compose. In particular, they chain on the right — that is, the last performed transformation is applied first:

canvas.translate(100, 100) # T_a canvas.rotate(pi / 4) # T_b canvas.translate(-100, -100) # T_c

As an example, say we want to rotate this square about its center by 45°

from scripton.canvas import Canvas canvas = Canvas(width=200, height=200, fill='gray') canvas.draw_rect(x=50, y=50, width=100, height=100, fill='gold')

Inserting a canvas.rotate is insufficient, since it rotates about the origin, which is currently situated at the top-left corner.

from scripton.canvas import Canvas from math import pi canvas = Canvas(width=200, height=200, fill='gray') canvas.rotate(pi / 4) canvas.draw_rect(x=50, y=50, width=100, height=100, fill='gold')

To rotate about the square's center (100, 100) instead, we first want to make it the new origin (0, 0). Translating by (-100, -100) accomplishes this. The composition order described above tells us that the last transform to appear in our code is applied first. So, we do this after the rotate call:

from scripton.canvas import Canvas from math import pi canvas = Canvas(width=200, height=200, fill='gray') canvas.rotate(pi / 4) canvas.translate(-100, -100) canvas.draw_rect(x=50, y=50, width=100, height=100, fill='gold')

Finally, we want to translate back the center to its original position. This should occur after the rotation has been performed. So, based on the composition ordering, this translation needs to appear before the rotation in our code:

from scripton.canvas import Canvas from math import pi canvas = Canvas(width=200, height=200, fill='gray') canvas.translate(100, 100) canvas.rotate(pi / 4) canvas.translate(-100, -100) canvas.draw_rect(x=50, y=50, width=100, height=100, fill='gold')

API

Canvas.scale

Composes a scaling transformation.

  • It accepts a scaling factor for each axis (x and y).
  • Providing a negative scaling factor flips the corresponding axis.
from scripton.canvas import Canvas canvas = Canvas(width=200, height=200) for _ in range(10): canvas.draw_rect( x=0, y=0, width=200, height=200, fill='#FFFFFF33' ) canvas.scale(x=0.9, y=0.9)

Canvas.rotate

Composes a rotation transformation.

  • The rotation angle should be in radians.
  • The rotation occurs about the current origin (by default the top-left corner).
from scripton.canvas import Canvas canvas = Canvas(width=200, height=200) for _ in range(10): canvas.draw_rect( x=0, y=0, width=200, height=200, fill='#FFFFFF33' ) canvas.rotate(0.1)

Canvas.translate

Composes a translation transformation.

from scripton.canvas import Canvas canvas = Canvas(width=200, height=200) for _ in range(10): canvas.draw_rect( x=0, y=0, width=100, height=100, fill='#FFFFFF33' ) canvas.translate(x=10, y=10)

Canvas.transform

The transform method accepts a transformation matrix and an optional keyword argument reset (by default False).

  • If reset is False (the default case), the given transformation matrix is composed with the existing transformation (as described here).
  • Otherwise, the given transformation matrix replaces the prior one.
from scripton.canvas import Canvas canvas = Canvas(width=200, height=200) canvas.transform( [ [2, 0, 50], [1, 2, 25] ] ) canvas.draw_rect(x=0, y=0, width=50, height=50, fill='white')

Canvas.reset_transform

Sets the current transformation to identity.

  • This is equivalent to canvas.transform([[1, 0, 0], [0, 1, 0]], reset=True).
from scripton.canvas import Canvas canvas = Canvas(width=200, height=200) canvas.transform( [ [2, 0, 50], [1, 2, 25] ] ) canvas.draw_rect(x=0, y=0, width=50, height=50, fill='white') canvas.reset_transform() canvas.draw_rect(x=0, y=0, width=50, height=50, fill='gold')