Skip to content

3. Shapes

import funground as f


def setup():
    f.size(640, 400)


def draw():
    f.background("white")
    f.fill("skyblue")
    f.rect(40, 60, 160, 100)            # top-left x, y, width, height
    f.fill("tomato")
    f.circle(320, 110, 100)             # centre x, y, diameter
    f.fill("gold")
    f.ellipse(520, 110, 160, 80)        # centre x, y, width, height
    f.stroke("navy")
    f.stroke_width(4)
    f.line(40, 260, 600, 340)           # from (x1, y1) to (x2, y2)
    f.stroke_width(12)
    for x in range(60, 600, 60):
        f.point(x, 230)


f.run()

The basic shapes

Function Placed by
f.rect(x, y, w, h) its top-left corner
f.circle(x, y, d) its centre; d is the diameter, not the radius
f.ellipse(x, y, w, h) its centre
f.line(x1, y1, x2, y2) its two ends; uses the stroke colour only
f.point(x, y) a dot, as wide as the stroke

Coordinates can have decimals — f.circle(100.5, 80.25, 40) — and edges are smoothed.

More shapes

import funground as f


def setup():
    f.size(640, 400)


def draw():
    f.background("white")
    f.fill("skyblue")
    f.square(30, 30, 100)                                  # top-left corner and size
    f.fill("gold")
    f.triangle(170, 130, 270, 130, 220, 30)                # three corners
    f.fill("seagreen")
    f.quad(310, 40, 420, 30, 400, 130, 330, 120)           # four corners, in order
    f.fill("orchid")
    f.polygon([(520, 30), (600, 70), (580, 130), (480, 130), (460, 70)])
    f.fill("tomato")
    f.arc(100, 280, 140, 140, 0, 270)                      # open (the default)
    f.arc(280, 280, 140, 140, 0, 270, "chord")
    f.arc(460, 280, 140, 140, 0, 270, "pie")


f.run()

More shapes

Function Notes
f.square(x, y, size) Placed by its top-left corner, like rect
f.triangle(...), f.quad(...) Corners in order; the shape is closed and filled
f.polygon(points) A list of (x, y) points; closed and filled
f.arc(x, y, w, h, start, stop, mode) Part of an ellipse centred at (x, y). Angles are in degrees, starting at the right (3 o'clock) and turning clockwise: 90 is straight down. "open" fills the slice but draws the outline only along the curve; "chord" closes it with a straight line; "pie" closes it through the centre

Placing shapes by their centre or corners

Each shape has a usual way to be placed: rect by its top-left corner, ellipse by its centre. You can change that. f.rect_mode(mode) changes how rect and square read their numbers. f.ellipse_mode(mode) does the same for ellipse, circle and arc. The setting stays until you change it again, and f.push() and f.pop() save and restore it.

Mode The four numbers mean
"corner" x, y is the top-left corner, then the width and height (the usual way for rect)
"center" x, y is the centre, then the width and height (the usual way for ellipse)
"radius" x, y is the centre, then half the width and half the height
"corners" two opposite corners: x1, y1, x2, y2, in any order

square and circle take one size, so "corners" works like "corner" for them.

f.image_mode(mode) does this for pictures: "corner" (the usual way), "center", or "corners", where you give both corners and so must give a width and height. Pictures are covered in 9. Paths, clipping and pictures.

import funground as f


def setup():
    f.size(640, 400)


def draw():
    f.background("white")
    f.fill("skyblue")
    f.rect_mode("center")
    f.rect(100, 100, 120, 60)              # centred on (100, 100)
    f.rect_mode("corners")
    f.rect(200, 60, 300, 140)              # from one corner to the opposite one
    f.fill("orchid")
    f.ellipse_mode("corner")
    f.ellipse(360, 60, 120, 80)            # its box starts at (360, 60)
    f.ellipse_mode("radius")
    f.ellipse(560, 100, 50, 30)            # radii, not widths


f.run()

The picture below draws the same four numbers under every mode. The red dot is (x, y).

Drawing modes

Rounded corners

Give rect or square one more number and all four corners are rounded by that radius. Give it four numbers and each corner gets its own radius. They go clockwise, starting at the top left: top-left, top-right, bottom-right, bottom-left. This works as it does in p5.

A radius cannot be negative, and you must give one radius or four. A radius that is too big is cut down to half the shorter side, so corners never overlap. If you use rect_mode, the box is placed first and the radii are applied after. The fill, the outline and any shadow all follow the curves. A path from f.path() has the same rect(x, y, w, h, radius), so you can join rounded shapes with union and the other path operations.

import funground as f


def setup():
    f.size(640, 400)


def draw():
    f.background(245)
    f.fill("lightsteelblue")
    f.stroke("navy")
    f.stroke_width(2)
    f.rect(30, 30, 160, 60, 12)                    # one radius for every corner
    f.fill("gold")
    f.rect(230, 30, 200, 80, 30, 30, 30, 4)        # four radii: one nearly sharp corner
    f.fill("tomato")
    f.square(30, 150, 100, 28)                     # square works the same way
    tag = f.path().rect(200, 160, 120, 80, 20)
    dot = f.path().circle(320, 170, 60)
    f.fill("seagreen")
    f.draw_path(tag | dot)                         # a rounded rectangle joined to a circle


f.run()

The picture below shows more: buttons, a speech bubble, a shadow and radii that are cut down.

Rounded corners

For any other outline, build it from points: see 9. Paths, clipping and pictures.

See also

Try it

  1. Draw a face: a big circle, two small circles for eyes and an f.arc() for the smile.
  2. Draw a row of five squares with a for loop. Hint: add a number to x each time round.
  3. Draw the same f.rect() four times, once with each f.rect_mode(). Where does each one land?

Next: 4. Colour