Clip regions in generativepy
Martin McBride, 2020-10-04
Tags generativepy tutorial path clipping
Categories generativepy generativepy tutorial

This tutorial covers the basics of using clip regions in generativepy. Clip regions are also known as clip paths.
A clip region defines the area of the page that you can draw on. If you draw a shape that is inside the clip region, it will be visible, if you draw a shape that is outside the clip region it will not be visible. If a shape is partly inside the clip region, it will be clipped so that only the part that is inside the region is visible. This can create interesting effects, especially with a complex clip region.
Clip regions are set by creating any Shape object. But instead of filling or stroking the shape, we use the clip()
method to install it as the current clip region.
Lifetime of a clip region
Clipping regions form part of the drawing context, in a similar way to Transform operations such as scaling or rotation. When a clip region is applied, it will affect all subsequent drawing operations.
If you apply a clip region, by default it will apply forever, so you will never be able to draw anything outside the clip region. If that is what you wish to do, it is perfectly valid.
However, you will often wish to apply a clip region while you are drawing a particular feature and then remove it. To do this, you should apply the clip region within a Transform
context (in other words, a with
block), as shown below. This means that the clip region will be deactivated on leaving the with
block.
Clipping example code
Here is a sample Python program that creates two different clip regions. The code is explained later in this article:
from generativepy.drawing import make_image, setup from generativepy.color import Color from generativepy.geometry import Circle, Square, Text, Transform def draw(ctx, width, height, frame_no, frame_count): setup(ctx, width, height, background=Color(0.8)) # Create a circular clip region and draw some squares in it with Transform(ctx): Circle(ctx).of_center_radius((190, 190), 100).clip() Square(ctx).of_corner_size((100, 100), 80).fill(Color('red')) Square(ctx).of_corner_size((100, 200), 80).fill(Color('green')) Square(ctx).of_corner_size((200, 100), 80).fill(Color('blue')) Square(ctx).of_corner_size((200, 200), 80).fill(Color('black')) with Transform(ctx): Text(ctx).of("ABC", (150, 350)).font("Times").size(150).align_left().align_top().clip() circles = [(200, 380, 'orange'), (200, 450, 'cyan'), (300, 380, 'green'), (300, 450, 'purple'), (400, 380, 'yellow'), (400, 450, 'blue')] for x, y, color in circles: Circle(ctx).of_center_radius((x, y), 70).fill(Color(color)) make_image("clip-tutorial.png", draw, 500, 500)
This code is available on github in tutorial/transforms/clip.py.
Here is the resulting image:
Circular clip-path
The first part of the code creates a circular clip region, and draws some squares inside it:
with Transform(ctx): Circle(ctx).of_center_radius((190, 190), 100).clip() Square(ctx).of_corner_size((100, 100), 80).fill(Color('red')) Square(ctx).of_corner_size((100, 200), 80).fill(Color('green')) Square(ctx).of_corner_size((200, 100), 80).fill(Color('blue')) Square(ctx).of_corner_size((200, 200), 80).fill(Color('black'))
First, we use a with
statement to create a transform context using a Transform
object. This will allow our clip-path to be automatically removed when we leave the with
block.
We then create a Circle
in the usual way, but instead of filling or stroking the shape, we call clip
. This sets the circle as the clip-path. Only the parts of the image inside the circle can be marked.
We then draw 4 squares that are partly inside and partly outside the clip region. This creates the shape at the top left of the image above. This consists of four squares of different colours, but any parts of the squares that are outside the circle are clipped.
When we leave the with
block, the clip-path is deactivated, so we can then draw on any part of the page.
Text clip-path
The second part of the code creates a complex clip region that takes the shape of the text string "ABC" drawn in a large font. The code then draws some overlapping coloured circles. These are clipped to the text shape:
with Transform(ctx): Text(ctx).of("ABC", (150, 350)).font("Times").size(150).align_left().align_top().clip() circles = [(200, 380, 'orange'), (200, 450, 'cyan'), (300, 380, 'green'), (300, 450, 'purple'), (400, 380, 'yellow'), (400, 450, 'blue')] for x, y, color in circles: Circle(ctx).of_center_radius((x, y), 70).fill(Color(color))
Complex clip paths
We can use composite paths or complex paths to create clip regions. The principle is exactly the same, we create the shape, but rather than filing or stroking the path, we use the clip()
method.
If a complex path contains "holes", then the holes may or may not be part of the clip region, depending on the fill rule. This is the same as described for filling complex paths.
Nested clip paths
If we apply a clip-path (Path A), and then apply another clip-path (Path B) while the original path is still active, the resulting clip-path will be the intersection of Path A and Path B. That is, we will only be able to draw on parts of the image that are inside both clip-paths.