Skip to main content

Images

PLL adds images to Python: you do not need to import anything to use them. If a line in your file is an expression that produces an image, PLL shows it in the interactions panel (and each picture there has a Save SVG button, if you want to keep it). Each function below is shown with its contract (the types of its inputs and output), followed by some examples and what they produce.

The type of an image, for type annotations, is Image:

def bullseye(size: float) -> Image:
"""a red circle on top of a larger white one, on top of an even larger red one"""
return overlay(circle(size, "solid", "red"),
circle(size * 2, "solid", "white"),
circle(size * 3, "solid", "red"))

bullseye(15)

Result of bullseye(15)

In all of the functions below, a mode is either "solid" or "outline", and a color is a string naming a color, like "red" (see Colors for the other ways to write colors). Sizes are in pixels, and can be ints or floats.

The pictures are drawn the way Racket's 2htdp/image library draws them: an outline is a 1-pixel pen just inside the shape, so a shape is the same size whether it is solid or outlined.

Every function checks what it is given, when it is called. A size that is text or negative, a misspelled mode or color, a list where the images themselves belong, or arguments in the wrong order each stop the program with a message that names the argument:

rectangle(40, "solid", 20, "red")
TypeError: rectangle's `height` (the 2nd argument) must be a number, but it is the string "solid". The arguments are: rectangle(width, height, mode, color).

Basic shapes​

circle​

circle(radius: float, mode: str, color: str) -> Image

A circle with the given radius.

circle(30, "outline", "red")

Result of circle(30, 'outline', 'red')

circle(20, "solid", "blue")

Result of circle(20, 'solid', 'blue')

square​

square(side: float, mode: str, color: str) -> Image

A square whose sides are side long.

square(40, "solid", "slateblue")

Result of square(40, 'solid', 'slateblue')

square(50, "outline", "darkmagenta")

Result of square(50, 'outline', 'darkmagenta')

rectangle​

rectangle(width: float, height: float, mode: str, color: str) -> Image

A rectangle that is width wide and height tall.

rectangle(40, 20, "outline", "black")

Result of rectangle(40, 20, 'outline', 'black')

rectangle(20, 40, "solid", "blue")

Result of rectangle(20, 40, 'solid', 'blue')

ellipse​

ellipse(width: float, height: float, mode: str, color: str) -> Image

An ellipse (an oval) that is width wide and height tall. If they are the same, it's a circle.

ellipse(60, 30, "outline", "black")

Result of ellipse(60, 30, 'outline', 'black')

ellipse(30, 60, "solid", "blue")

Result of ellipse(30, 60, 'solid', 'blue')

triangle​

triangle(side: float, mode: str, color: str) -> Image

A triangle with three sides that are each side long, pointing up.

triangle(40, "solid", "tan")

Result of triangle(40, 'solid', 'tan')

right_triangle​

right_triangle(width: float, height: float, mode: str, color: str) -> Image

A triangle with a right angle at the bottom left, whose bottom side is width long, and whose left side is height tall.

right_triangle(36, 48, "solid", "black")

Result of right_triangle(36, 48, 'solid', 'black')

regular_polygon​

regular_polygon(side: float, sides: int, mode: str, color: str) -> Image

A shape with sides sides, each side long, all at the same angles.

regular_polygon(50, 3, "outline", "red")

Result of regular_polygon(50, 3, 'outline', 'red')

regular_polygon(40, 4, "outline", "blue")

Result of regular_polygon(40, 4, 'outline', 'blue')

regular_polygon(20, 8, "solid", "red")

Result of regular_polygon(20, 8, 'solid', 'red')

star​

star(side: float, mode: str, color: str) -> Image

A five-pointed star, made by connecting every other corner of a five-sided regular_polygon whose sides are side long.

star(40, "solid", "firebrick")

Result of star(40, 'solid', 'firebrick')

star_polygon​

star_polygon(side: float, points: int, step: int, mode: str, color: str) -> Image

A star with points points: it is made by drawing a line from each corner of a points-sided regular_polygon (with sides side long) to the corner step corners further around.

star_polygon(40, 5, 2, "solid", "seagreen")

Result of star_polygon(40, 5, 2, 'solid', 'seagreen')

star_polygon(40, 7, 3, "outline", "darkred")

Result of star_polygon(40, 7, 3, 'outline', 'darkred')

line​

line(dx: float, dy: float, color: str) -> Image

A line that goes dx pixels to the right (or to the left, if dx is negative), and dy pixels down (or up, if dy is negative).

line(30, 30, "black")

Result of line(30, 30, 'black')

line(-30, 20, "red")

Result of line(-30, 20, 'red')

text​

text(string: str, size: float, color: str) -> Image

The string, written in letters size pixels tall. It is set in a monospace font (every letter is the same width), so its width is exact, and its spaces are kept.

text("Hello", 24, "olive")

Result of text('Hello', 24, 'olive')

text("Goodbye", 36, "indigo")

Result of text('Goodbye', 36, 'indigo')

empty_image​

empty_image: Image

An image with nothing in it, 0 pixels wide and 0 pixels tall. Note that it is not a function: write empty_image, not empty_image(). It's useful as a starting point when you build up an image a piece at a time.

image_width(empty_image)
0

Combining images​

beside​

beside(image1: Image, image2: Image, ...) -> Image

The images next to each other, left to right, lined up along their centers. It can be given any number of images.

beside(ellipse(20, 70, "solid", "gray"),
ellipse(20, 50, "solid", "darkgray"),
ellipse(20, 30, "solid", "dimgray"),
ellipse(20, 10, "solid", "black"))

Result of beside(ellipse(20, 70, 'solid', 'gray'), ellipse(20, 50, 'solid', 'darkgray'), ellipse(20, 30, 'solid', 'dimgray'), ellipse(20, 10, 'solid', 'black'))

above​

above(image1: Image, image2: Image, ...) -> Image

The images stacked, with image1 on top, lined up along their centers. It can be given any number of images.

above(ellipse(70, 20, "solid", "gray"),
ellipse(50, 20, "solid", "darkgray"),
ellipse(30, 20, "solid", "dimgray"),
ellipse(10, 20, "solid", "black"))

Result of above(ellipse(70, 20, 'solid', 'gray'), ellipse(50, 20, 'solid', 'darkgray'), ellipse(30, 20, 'solid', 'dimgray'), ellipse(10, 20, 'solid', 'black'))

overlay​

overlay(image1: Image, image2: Image, ...) -> Image

The images on top of each other, with their centers lined up, and image1 on top. (So if image1 is bigger than the others, they can't be seen!) It can be given any number of images.

overlay(rectangle(30, 60, "solid", "orange"),
ellipse(60, 30, "solid", "purple"))

Result of overlay(rectangle(30, 60, 'solid', 'orange'), ellipse(60, 30, 'solid', 'purple'))

overlay(ellipse(10, 10, "solid", "red"),
ellipse(20, 20, "solid", "black"),
ellipse(30, 30, "solid", "red"),
ellipse(40, 40, "solid", "black"),
ellipse(50, 50, "solid", "red"),
ellipse(60, 60, "solid", "black"))

Result of ellipse(60, 60, 'solid', 'black'))

underlay​

underlay(image1: Image, image2: Image, ...) -> Image

Like overlay, but with image1 on the bottom.

underlay(rectangle(30, 60, "solid", "orange"),
ellipse(60, 30, "solid", "purple"))

Result of underlay(rectangle(30, 60, 'solid', 'orange'), ellipse(60, 30, 'solid', 'purple'))

Lining up edges​

These work like the functions above, but their first input(s) say which edges of the images line up. A horizontal place (x_place) is "left", "center", or "right", and a vertical place (y_place) is "top", "center", or "bottom".

beside_align​

beside_align(y_place: str, image1: Image, image2: Image, ...) -> Image

Like beside, but lined up at the y_place edge.

beside_align("bottom",
ellipse(20, 70, "solid", "lightsteelblue"),
ellipse(20, 50, "solid", "mediumslateblue"),
ellipse(20, 30, "solid", "slateblue"),
ellipse(20, 10, "solid", "navy"))

Result of ellipse(20, 10, 'solid', 'navy'))

beside_align("top",
ellipse(20, 70, "solid", "mediumorchid"),
ellipse(20, 50, "solid", "darkorchid"),
ellipse(20, 30, "solid", "purple"),
ellipse(20, 10, "solid", "indigo"))

Result of ellipse(20, 10, 'solid', 'indigo'))

above_align​

above_align(x_place: str, image1: Image, image2: Image, ...) -> Image

Like above, but lined up at the x_place edge.

above_align("right",
ellipse(70, 20, "solid", "gold"),
ellipse(50, 20, "solid", "goldenrod"),
ellipse(30, 20, "solid", "darkgoldenrod"),
ellipse(10, 20, "solid", "sienna"))

Result of ellipse(10, 20, 'solid', 'sienna'))

above_align("left",
ellipse(70, 20, "solid", "yellowgreen"),
ellipse(50, 20, "solid", "olivedrab"),
ellipse(30, 20, "solid", "darkolivegreen"),
ellipse(10, 20, "solid", "darkgreen"))

Result of ellipse(10, 20, 'solid', 'darkgreen'))

overlay_align​

overlay_align(x_place: str, y_place: str, image1: Image, image2: Image, ...) -> Image

Like overlay, but lined up at the x_place and y_place edges: e.g., "right", "bottom" puts the images' bottom right corners together.

overlay_align("left", "center",
rectangle(30, 60, "solid", "orange"),
ellipse(60, 30, "solid", "purple"))

Result of overlay_align('left', 'center', rectangle(30, 60, 'solid', 'orange'), ellipse(60, 30, 'solid', 'purple'))

overlay_align("right", "bottom",
rectangle(20, 20, "solid", "silver"),
rectangle(30, 30, "solid", "seagreen"),
rectangle(40, 40, "solid", "silver"),
rectangle(50, 50, "solid", "seagreen"))

Result of rectangle(50, 50, 'solid', 'seagreen'))

underlay_align​

underlay_align(x_place: str, y_place: str, image1: Image, image2: Image, ...) -> Image

Like overlay_align, but with image1 on the bottom.

underlay_align("right", "top",
rectangle(50, 50, "solid", "seagreen"),
rectangle(40, 40, "solid", "silver"),
rectangle(30, 30, "solid", "seagreen"),
rectangle(20, 20, "solid", "silver"))

Result of rectangle(20, 20, 'solid', 'silver'))

Placing images exactly​

overlay_xy​

overlay_xy(image1: Image, dx: float, dy: float, image2: Image) -> Image

image1 on top of image2, with image2 moved dx pixels to the right and dy pixels down from image1's top left corner. Negative numbers move it left or up. The result is as big as it needs to be to hold both images: nothing is cut off.

overlay_xy(rectangle(20, 20, "outline", "black"),
20, 0,
rectangle(20, 20, "outline", "black"))

Result of overlay_xy(rectangle(20, 20, 'outline', 'black'), 20, 0, rectangle(20, 20, 'outline', 'black'))

overlay_xy(rectangle(20, 20, "solid", "red"),
10, 10,
rectangle(20, 20, "solid", "black"))

Result of overlay_xy(rectangle(20, 20, 'solid', 'red'), 10, 10, rectangle(20, 20, 'solid', 'black'))

overlay_xy(rectangle(20, 20, "solid", "red"),
-10, -10,
rectangle(20, 20, "solid", "black"))

Result of overlay_xy(rectangle(20, 20, 'solid', 'red'), -10, -10, rectangle(20, 20, 'solid', 'black'))

underlay_xy​

underlay_xy(image1: Image, dx: float, dy: float, image2: Image) -> Image

Like overlay_xy, but with image1 on the bottom.

underlay_xy(rectangle(20, 20, "solid", "red"),
10, 10,
rectangle(20, 20, "solid", "black"))

Result of underlay_xy(rectangle(20, 20, 'solid', 'red'), 10, 10, rectangle(20, 20, 'solid', 'black'))

Scenes​

A scene is a fixed-size canvas, which is useful for drawing the frames of an animation. In a scene (and in images in general), the point (0, 0) is the top left corner: increasing x moves right, and increasing y moves down.

empty_scene​

empty_scene(width: float, height: float) -> Image

An empty, white scene that is width wide and height tall, with a thin outline.

empty_scene(160, 90)

Result of empty_scene(160, 90)

place_image​

place_image(image: Image, x: float, y: float, scene: Image) -> Image

scene, with image placed so that its center is at (x, y). Unlike overlay_xy, the result is the same size as scene: anything that goes past its edges is cut off.

place_image(triangle(32, "solid", "red"), 24, 24, empty_scene(48, 48))

Result of place_image(triangle(32, 'solid', 'red'), 24, 24, empty_scene(48, 48))

place_image(circle(8, "solid", "tomato"), 0, 0, empty_scene(48, 48))

Result of place_image(circle(8, 'solid', 'tomato'), 0, 0, empty_scene(48, 48))

place_image(circle(4, "solid", "white"), 18, 6,
place_image(circle(4, "solid", "white"), 0, 6,
place_image(circle(4, "solid", "white"), 14, 32,
rectangle(48, 48, "solid", "navy"))))

Result of place_image(circle(4, 'solid', 'white'), 18, 6, place_image(circle(4, 'solid', 'white'), 0, 6, place_image(circle(4, 'solid', 'white'), 14, 32, rectangle(48, 48, 'solid', 'navy'))))

crop​

crop(x: float, y: float, width: float, height: float, image: Image) -> Image

The width by height piece of image whose top left corner is at (x, y).

crop(0, 0, 40, 40, circle(40, "solid", "chocolate"))

Result of crop(0, 0, 40, 40, circle(40, 'solid', 'chocolate'))

crop(40, 60, 40, 60, ellipse(80, 120, "solid", "dodgerblue"))

Result of crop(40, 60, 40, 60, ellipse(80, 120, 'solid', 'dodgerblue'))

frame​

frame(image: Image) -> Image

image, with a black outline around its edges.

frame(ellipse(40, 40, "solid", "gray"))

Result of frame(ellipse(40, 40, 'solid', 'gray'))

Transforming images​

rotate​

rotate(angle: float, image: Image) -> Image

image, turned angle degrees counter-clockwise (or clockwise, if angle is negative). The result is as big as it needs to be to hold the turned shapes, so a circle turned is as wide as it was. The angle comes first:

rotate(square(20, "solid", "red"), 45)
TypeError: rotate takes the `angle` first, then the image: write rotate(45, image).
rotate(45, ellipse(60, 20, "solid", "olivedrab"))

Result of rotate(45, ellipse(60, 20, 'solid', 'olivedrab'))

rotate(5, rectangle(50, 50, "outline", "black"))

Result of rotate(5, rectangle(50, 50, 'outline', 'black'))

rotate(-90, triangle(40, "solid", "seagreen"))

Result of rotate(-90, triangle(40, 'solid', 'seagreen'))

scale​

scale(factor: float, image: Image) -> Image

image, made factor times as big: e.g., 2 is twice as wide and twice as tall, and 0.5 is half as wide and half as tall.

scale(2, ellipse(20, 30, "solid", "blue"))

Result of scale(2, ellipse(20, 30, 'solid', 'blue'))

scale(0.5, ellipse(20, 30, "solid", "blue"))

Result of scale(0.5, ellipse(20, 30, 'solid', 'blue'))

flip_horizontal​

flip_horizontal(image: Image) -> Image

image, mirrored left to right.

beside(rotate(30, square(50, "solid", "red")),
flip_horizontal(rotate(30, square(50, "solid", "blue"))))

Result of beside(rotate(30, square(50, 'solid', 'red')), flip_horizontal(rotate(30, square(50, 'solid', 'blue'))))

flip_vertical​

flip_vertical(image: Image) -> Image

image, mirrored top to bottom.

above(star(40, "solid", "firebrick"),
scale(0.5, flip_vertical(star(40, "solid", "gray"))))

Result of above(star(40, 'solid', 'firebrick'), scale(0.5, flip_vertical(star(40, 'solid', 'gray'))))

Sizes​

image_width​

image_width(image: Image) -> int

How many pixels wide image is.

image_width(ellipse(30, 40, "solid", "orange"))
30
image_width(beside(circle(10, "solid", "red"), circle(20, "solid", "blue")))
60

image_height​

image_height(image: Image) -> int

How many pixels tall image is.

image_height(ellipse(30, 40, "solid", "orange"))
40
image_height(overlay(circle(20, "solid", "orange"), circle(30, "solid", "purple")))
60

Comparing images​

Two images are == when they draw the same shapes, in the same places, in the same colors, however they were made, so "red" and (255, 0, 0) are the same color:

circle(20, "solid", "red") == circle(20, "solid", (255, 0, 0))
True
beside(square(10, "solid", "red"), square(10, "solid", "blue")) == beside(square(10, "solid", "blue"), square(10, "solid", "red"))
False

So you can test a function that draws something by comparing what it draws with the image it should draw:

def dot(r: float) -> Image:
"""a red dot of radius r"""
return circle(r, "solid", "red")

def test_dot():
assert dot(3) == circle(3, "solid", "red")
assert image_width(dot(5)) == 10

Loading pictures​

load_image​

load_image(source: str) -> Image

A picture, read from a file in the same folder as your program (when source is a file name, like "cat.png"), or from an address on the web (when source starts with https://). It gives you an ordinary image, so everything above works on it. PNG, JPEG, GIF, WebP, and SVG files are understood, up to 2 MB each: these are for the graphics a program draws with, not for photographs.

doghouse = load_image("https://raw.githubusercontent.com/neu-pdi/cs2000-public-resources/refs/heads/main/static/img/lab5-doghouse.png")
scale(0.25, doghouse)

Result of doghouse = load_image('https://raw.githubusercontent.com/neu-pdi/cs2000-public-resources/refs/heads/main/static/img/lab5-doghouse.png') scale(0.25, doghouse)

beside(scale(0.2, doghouse), scale(0.2, flip_horizontal(doghouse)))

Result of beside(scale(0.2, doghouse), scale(0.2, flip_horizontal(doghouse)))

In the browser, an address on the web only works if its site allows other sites to read it. If loading fails, download the picture, add it to your repository (next to your program), and load it by its file name instead.

Colors​

A color can be:

  • the name of a color, like "red", "navy", or "gold" (any CSS color name works, and so does "transparent");
  • a hex code, like "#ff8800" or "#f80";
  • "rgb(255, 136, 0)" or "hsl(32, 100%, 50%)", or "rgba(...)" and "hsla(...)" with a fourth number from 0.0 to 1.0 saying how solid it is;
  • a tuple (red, green, blue), with each from 0 to 255, like (255, 136, 0);
  • a tuple (red, green, blue, alpha), where alpha says how solid the color is: a whole number from 0 (invisible) to 255 (solid), or a fraction from 0.0 to 1.0, so (255, 0, 0, 128) and (255, 0, 0, 0.5) are both half see-through.

A color name is checked against the CSS names, so a misspelled one stops the program and suggests the name you meant, rather than drawing nothing:

circle(20, "solid", "bleu")
ValueError: circle's `color` (the 3rd argument) is the string "bleu", which is not a color name PLL knows. Did you mean "blue"?
beside(circle(20, "solid", "darkorange"),
circle(20, "solid", "#ff8800"),
circle(20, "solid", (255, 136, 0)))

Result of beside(circle(20, 'solid', 'darkorange'), circle(20, 'solid', '#ff8800'), circle(20, 'solid', (255, 136, 0)))

overlay(circle(20, "solid", (0, 0, 255, 0.5)),
rectangle(60, 20, "solid", "gold"))

Result of overlay(circle(20, 'solid', (0, 0, 255, 0.5)), rectangle(60, 20, 'solid', 'gold'))