Easier to read with cream paper, a tint, more contrast or larger text? Choose your colours and text, once, for every page.

Free lesson

Filling a triangle with edge tests

After this lesson: I can decide which pixels are inside a triangle with edge tests and find a point's barycentric weights.

60 min lesson3 commandsComputer vision lab: pixels, thresholds and counting objects in JupyterLab

A triangle is filled by testing pixels

A triangle is the basic shape of computer graphics. Games, films and design tools build every surface out of thousands of them, and each one must be filled with pixels.

In this lesson you learn how a program decides whether a pixel is inside a triangle, using three simple tests.

By the end of this lesson

Note

I can decide which pixels are inside a triangle with edge tests and find a point's barycentric weights.

Key words

WordWhat it means
Edgethe line between two corners
Edge testa number that says which side of an edge a point is on
Bounding boxthe smallest rectangle that holds a shape
Barycentric weightsthree amounts that locate a point inside a triangle
Interpolateto work out a value between known values

The cells students fill in are left blank in this preview.

A number tells which side of a line

Walk from one corner of a triangle to the next. Everything on your left is on one side of that edge, and everything on your right is on the other.

The edge test gives a number for a point p and an edge from a to b. Let dx and dy be the edge's run and rise, and px and py the point's offset from a. The number is dx times py, minus dy times px. Zero means p is exactly on the line.

Worked example: edge values for three points

PointEdge 1Edge 2Edge 3Inside?
(2, 2)163216Yes, all positive
(4, 4)32032Yes, on an edge
(6, 6)48-3248No, one is negative

The cells students fill in are left blank in this preview.

Work out the three edge values

Command
docker exec jupyter python3 -c "e = lambda a, b, p: (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]); a, b, c = (0, 0), (8, 0), (0, 8); print([e(a, b, (2, 2)), e(b, c, (2, 2)), e(c, a, (2, 2))])"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

A point is inside when all three agree

The Run prints [16, 32, 16], the first row of the table. A point is inside the triangle when the three edge values are all positive, or all negative, or zero.

Which sign it is depends on whether the corners go clockwise or anticlockwise. Accepting either direction makes the test work for any triangle.

Test each pixel in the bounding box

To fill the triangle, test pixels one at a time and colour the ones that pass. Testing the whole picture is wasteful, so we loop only over the bounding box, the smallest rectangle around the three corners.

Real graphics chips do exactly this, testing thousands of pixels at once.

Count the pixels of the big triangle

Command
docker exec jupyter python3 -c "e = lambda a, b, p: (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]); a, b, c = (0, 0), (8, 0), (0, 8); print(sum(1 for x in range(9) for y in range(9) if min(e(a, b, (x, y)), e(b, c, (x, y)), e(c, a, (x, y))) >= 0))"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

A common mistake

Warning

Many people think one edge test is enough. One edge only splits the picture into two halves, and half of it is not a triangle. A point is inside only when it is on the inner side of all three edges. If any one test fails, the point is outside.

Fill a triangle in text

Code: python
def edge(a, b, p):
    return (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0])

corners = [[1, 1], [9, 2], [3, 6]]
a = corners[0]
b = corners[1]
c = corners[2]

for y in range(8):
    line = ""
    for x in range(11):
        p = [x, y]
        first = edge(a, b, p)
        second = edge(b, c, p)
        third = edge(c, a, p)
        if first >= 0 and second >= 0 and third >= 0:
            line = line + "#"
        elif first <= 0 and second <= 0 and third <= 0:
            line = line + "#"
        else:
            line = line + "."
    print(line)
# Change the corner [3, 6] to [3, 7] and press Run.

Barycentric weights say how near each corner

The same three numbers also say where a point sits. Divide each edge value by the number for the whole triangle, which is the edge test of the third corner. The three results are the barycentric weights.

They add up to 1. Each is large when the point is near its corner. A negative weight means the point is outside.

Worked example: weights of (2, 2)

CornerEdge value usedDivided by 64Weight
(0, 0)3232 / 640.5
(8, 0)1616 / 640.25
(0, 8)1616 / 640.25

The cells students fill in are left blank in this preview.

Weights blend colours across a triangle

Weights let us interpolate. Say the corners are red, green and blue. A pixel takes red times its first weight, plus green times its second, plus blue times its third.

Pixels near the red corner come out mostly red, and in the middle all three mix. This is how a graphics chip smooths colour across a surface.

Draw a filled triangle with Pillow

Command
docker exec jupyter python3 -c "from PIL import Image, ImageDraw; p = Image.new('L', (40, 40), 0); ImageDraw.Draw(p).polygon([(2, 2), (34, 2), (2, 34)], fill=200); p.save('triangle.png'); print(p.getpixel((10, 10)), p.getpixel((30, 30)))"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Open triangle.png in JupyterLab

The Run prints 200 0 and saves triangle.png. Open it from the file list.

You see a black square with a light grey right-angled triangle in it. The right angle is at the top left, and the long slanting edge runs from the top right to the bottom left.

About this lab and its source: Computer vision lab: pixels, thresholds and counting objects in JupyterLab

Teaching a class? Set this module for your students with a teacher guide, marked assignments and a lab for every student.