Skip to content

Lab 12: Winking with a Smile

A closed eye is an arc — the top half of an ellipse, drawn with quadrant mask TOP_HALF (3) instead of a full circle. Only the right eye closes here; the left stays open, which is what makes it read as a wink instead of a blink.

Sample Program Code

The left eye and the smile are drawn once and never touched again; only the right eye's box gets erased and rebuilt:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
# Lab 12: Winking with a Smile
# A closed eye is an arc -- the top half of an ellipse, drawn with
# quadrant mask TOP_HALF (3) instead of a full circle. Only one eye
# closes; the other stays open, which is what makes it read as a wink
# instead of a blink.
#
# Only the right eye changes between the two frames, so only the right
# eye's box gets erased and rebuilt. The left eye and the smile are drawn
# once and then left alone for as long as the program runs.

import config
import shapes
from utime import sleep

display = config.init_display()
WHITE = config.WHITE
BLACK = config.BLACK
NO_FILL = config.NO_FILL
FILL = config.FILL

TOP_HALF = 3      # 1 (top right) + 2 (top left)
BOTTOM_HALF = 12  # 4 (bottom left) + 8 (bottom right)

HALF_WIDTH = config.WIDTH // 2
EYE_SPACING = 48
LEFT_EYE_X = HALF_WIDTH - EYE_SPACING
RIGHT_EYE_X = HALF_WIDTH + EYE_SPACING
EYE_Y = 100
EYE_RADIUS = 28
PUPIL_RADIUS = 10

WINK_RADIUS_Y = 14
WINK_Y = EYE_Y + 7
STROKE = 4

MOUTH_Y = 168
MOUTH_RADIUS_X = 48
MOUTH_RADIUS_Y = 24

# Big enough to cover an open eye, which is the largest thing that ever
# appears in this box.
EYE_BOX = EYE_RADIUS + 4


def draw_open_eye(x):
    shapes.ellipse(display, x, EYE_Y, EYE_RADIUS, EYE_RADIUS, WHITE, FILL)
    shapes.ellipse(display, x, EYE_Y, PUPIL_RADIUS, PUPIL_RADIUS, BLACK, FILL)


def draw_winking_eye(x):
    for offset in range(STROKE):
        shapes.ellipse(display, x, WINK_Y + offset, EYE_RADIUS,
                       WINK_RADIUS_Y, WHITE, NO_FILL, TOP_HALF)


def draw_smile():
    for offset in range(STROKE):
        shapes.ellipse(display, HALF_WIDTH, MOUTH_Y - offset,
                       MOUTH_RADIUS_X, MOUTH_RADIUS_Y,
                       WHITE, NO_FILL, BOTTOM_HALF)


def erase_eye(x):
    display.fill_rect(x - EYE_BOX, EYE_Y - EYE_BOX,
                      EYE_BOX * 2, EYE_BOX * 2, BLACK)


def draw_resting_face():
    """Everything, once. After this only the right eye is ever touched."""
    display.fill(BLACK)
    draw_open_eye(LEFT_EYE_X)
    draw_open_eye(RIGHT_EYE_X)
    draw_smile()


def set_right_eye(winking):
    erase_eye(RIGHT_EYE_X)
    if winking:
        draw_winking_eye(RIGHT_EYE_X)
    else:
        draw_open_eye(RIGHT_EYE_X)


draw_resting_face()

while True:
    set_right_eye(True)    # eye snaps shut
    sleep(0.35)            # hold the wink just long enough to be seen
    set_right_eye(False)   # eye opens again
    sleep(2.5)             # normal face until the next wink

# Things to try:
#
# 1. The wink is now two small rectangles of work instead of two whole
#    screens. Add a print() of ticks_ms() around set_right_eye() and see
#    how little time a frame actually takes when you only redraw the part
#    that moved.
#
# 2. Close the LEFT eye instead. On a face, left and right are not
#    interchangeable -- most people read a left-eye wink as slightly
#    different in tone. Try it on a few people.

Here's what that program draws while winking:

Simulated output of 12-wink.py

One Eye, Not the Whole Face

Because only the right eye ever changes, set_right_eye() erases and redraws just that one region — a small rectangle, not the screen. Time it on real hardware and a wink becomes two small boxes of work instead of two whole screens, which is exactly what keeps this animation from stuttering the way a full-screen redraw would.

Left and right aren't interchangeable on a face. Try closing the left eye instead of the right and show it to a few people — most read the two as having a slightly different tone, even though the geometry is a mirror image. Faces carry more information than the code that draws them admits to.