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.

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

Prev

Popular tags

2d arrays abstract data type alignment and angle animation arange arc array arrays 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 design pattern device space dictionary drawing duck typing efficiency ellipse else encryption enumerate fill filter font font style for loop 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 len line linear gradient linspace list list comprehension logical operator lru_cache magic method mandelbrot mandelbrot set map matplotlib monad mutability named parameter numeric python numpy object open operator optimisation optional parameter or pandas partial application path pattern permutations polygon positional parameter print pure function python standard library radial gradient range recipes rectangle recursion reduce repeat rgb rotation roundrect scaling scipy sector segment sequence setup shape singleton slice slicing sound spirograph sprite square str stream string stroke structural pattern subpath symmetric encryption template text text metrics tinkerbell fractal transform translation transparency triangle truthy value tuple turtle unpacking user space vectorisation webserver website while loop zip