Esempio n. 1
0
def main(num_iterations=sys.maxsize):
    global device
    port = 1
    bus = smbus.SMBus(port)
    apds = APDS9960(bus)
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(7, GPIO.IN)
    #GPIO.add_event_detect(7, GPIO.FALLING, callback = intH)
    apds.setProximityIntLowThreshold(50)
    print("Gesture Test")
    print("============")
    apds.enableGestureSensor()
    device = get_device()
    regulator = framerate_regulator(fps=1)
    font_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'fonts', 'Volter__28Goldfish_29.ttf'))
    font = ImageFont.truetype(font_path,35)
    #draw.text((0, 0), "Hello World", font=font, fill=255)
    dispay_message(font,6,'Start')
    t = threading.Thread(target=displaytimeout)
    t.start()
    i = 100
    #for code in infinite_shuffle(codes):
    while True:
        with regulator:
            num_iterations -= 1
            if num_iterations == 0:
                break
            if apds.isGestureAvailable():
                motion = apds.readGesture()
                if motion == APDS9960_DIR_LEFT:
                    i = i+1
                if motion == APDS9960_DIR_RIGHT:
                    i = i-1
                code = codes[i]
                display_icon(code, i)
Esempio n. 2
0
    def _ShowAchievement(self, achievement):
        """Displays an achievement"""
        img_path = os.path.abspath(achievements.DEFAULT_ACHIEVEMENT_FRAME)
        background = PIL.Image.new('RGB', self.luma_device.size, 'black')
        logo = PIL.Image.open(img_path).convert('RGB')
        background.paste(logo)

        text_layer = PIL.ImageDraw.Draw(background)
        _font = PIL.ImageFont.load_default()

        _, text_height = text_layer.textsize(achievement.message)
        split_message = achievement.Splitted()
        text_layer.text((44, 4), split_message[0], (255, 255, 255), font=_font)
        text_layer.text((44, 4 + text_height),
                        split_message[1], (255, 255, 255),
                        font=_font)
        text_layer.text((44, 4 + text_height * 2),
                        split_message[2], (255, 255, 255),
                        font=_font)

        _font = PIL.ImageFont.truetype('assets/fonts/pixelmix.ttf', 16)
        text_layer.text((5, 8 + text_height * 3),
                        achievement.big_message, (255, 255, 255),
                        font=_font)

        _font = PIL.ImageFont.truetype('assets/fonts/NotoEmoji-Regular.ttf',
                                       28)
        text_layer.text((4, 4), achievement.emoji, (255, 255, 255), font=_font)

        regulator = framerate_regulator(fps=5)
        for _ in range(15):  # 15 frames at 5fps
            with regulator:
                self.luma_device.display(
                    background.convert(self.luma_device.mode))
Esempio n. 3
0
def main():
    device = get_device()
    start_time = time.time()
    regulator = framerate_regulator(fps=10)

    while True:
        with regulator:
            delta = (time.time() - start_time)

            # Offset is a sine wave derived from the time delta
            # we use this to animate both the hue and larson scan
            # so they are kept in sync with each other
            offset = (math.sin(delta * SCAN_SPEED) + 1) / 2

            # Use offset to pick the right colour from the hue wheel
            hue = int(round(offset * 360))

            # Now we generate a value from 0 to 7
            offset = int(round(offset * device.width))

            with canvas(device, dither=True) as draw:
                for x in range(device.width):
                    sat = 1.0

                    val = (device.width - 1) - (abs(offset - x) * FALLOFF)
                    val /= (device.width - 1)  # Convert to 0.0 to 1.0
                    val = max(val, 0.0)  # Ditch negative values

                    xhue = hue  # Grab hue for this pixel
                    xhue += (1 - val) * 10  # Use the val offset to give a slight colour trail variation
                    xhue %= 360  # Clamp to 0-359
                    xhue /= 360.0  # Convert to 0.0 to 1.0

                    r, g, b = [int(c * 255) for c in colorsys.hsv_to_rgb(xhue, sat, val)]
                    draw.line((x, 0, x, device.height), fill=(r, g, b, int(val * 255)))
Esempio n. 4
0
def main():
    regulator = framerate_regulator(fps=30)
    fonts = [make_font("code2000.ttf", sz) for sz in range(24, 8, -2)]
    sq = device.width * 2
    virtual = viewport(device, sq, sq)

    color_gen = pairs(infinite_shuffle(colors))

    for welcome_a, welcome_b in pairs(infinite_shuffle(welcome)):
        color_a, color_b = next(color_gen)
        widget_a = make_snapshot(device.width, device.height, welcome_a, fonts, color_a)
        widget_b = make_snapshot(device.width, device.height, welcome_b, fonts, color_b)

        posn_a = random_point(virtual.width - device.width, virtual.height - device.height)
        posn_b = random_point(virtual.width - device.width, virtual.height - device.height)

        while overlapping(posn_a, posn_b, device.width, device.height):
            posn_b = random_point(virtual.width - device.width, virtual.height - device.height)

        virtual.add_hotspot(widget_a, posn_a)
        virtual.add_hotspot(widget_b, posn_b)

        for _ in range(30):
            with regulator:
                virtual.set_position(posn_a)

        for posn in lerp_2d(posn_a, posn_b, device.width // 4):
            with regulator:
                virtual.set_position(posn)

        virtual.remove_hotspot(widget_a, posn_a)
        virtual.remove_hotspot(widget_b, posn_b)
Esempio n. 5
0
    def sprite_loop(self, num_iterations=sys.maxsize):
        colors = ["red", "orange", "yellow", "green", "blue", "magenta"]
        balls = [
            GDisp_st7735.Ball(self.device.width, self.device.height, i * 1.5,
                              colors[i % 6]) for i in range(10)
        ]

        frame_count = 0
        fps = ""
        canvas = luma.core.render.canvas(self.device)

        regulator = framerate_regulator(fps=10)

        while num_iterations > 0:
            with regulator:
                num_iterations -= 1

                frame_count += 1
                with canvas as c:
                    c.rectangle(self.device.bounding_box,
                                outline="white",
                                fill="black")
                    for b in balls:
                        b.update_pos()
                        b.draw(c)
                    c.text((2, 0), fps, fill="white")
Esempio n. 6
0
def matrix(device):
    wrd_rgb = [(154, 173, 154), (0, 255, 0), (0, 235, 0), (0, 220, 0),
               (0, 185, 0), (0, 165, 0), (0, 128, 0), (0, 0, 0),
               (154, 173, 154), (0, 145, 0), (0, 125, 0), (0, 100, 0),
               (0, 80, 0), (0, 60, 0), (0, 40, 0), (0, 0, 0)]

    clock = 0
    blue_pilled_population = []
    max_population = device.width * 8
    regulator = framerate_regulator(fps=10)

    def increase_population():
        blue_pilled_population.append(
            [randint(0, device.width), 0,
             gauss(1.2, 0.6)])

    while True:
        clock += 1
        with regulator:
            with canvas(device, dither=True) as draw:
                for person in blue_pilled_population:
                    x, y, speed = person
                    for rgb in wrd_rgb:
                        if 0 <= y < device.height:
                            draw.point((x, y), fill=rgb)
                        y -= 1
                    person[1] += speed

        if clock % 5 == 0 or clock % 3 == 0:
            increase_population()

        while len(blue_pilled_population) > max_population:
            blue_pilled_population.pop(0)
Esempio n. 7
0
    def _ShowDefaultScan(self, name):
        """Show the default scan animation"""
        size = [min(*self.luma_device.size)] * 2
        posn = ((self.luma_device.width - size[0]) // 2,
                self.luma_device.height - size[1])
        regulator = framerate_regulator(fps=30)
        image = PIL.Image.open(DEFAULT_SCAN_GIF)

        total_drunk = self._database.GetAmountFromName(name)

        default_msg = 'Cheers ' + name + '!'
        default_msg += ' {0:s}L'.format(
            utils.GetShortAmountOfBeer(total_drunk / 100.0))

        for gif_frame in PIL.ImageSequence.Iterator(image):
            with regulator:
                background = PIL.Image.new('RGB', self.luma_device.size,
                                           'black')
                # Add a frame from the animation
                background.paste(
                    gif_frame.resize(size, resample=PIL.Image.LANCZOS), posn)

                # Add a text layer over the frame
                text_layer = PIL.ImageDraw.Draw(background)
                text_width, text_height = text_layer.textsize(default_msg)
                text_pos = ((self.luma_device.width - text_width) // 2,
                            self.luma_device.height - text_height)
                text_layer.text(text_pos,
                                default_msg, (255, 255, 255),
                                font=self._font)

                self.luma_device.display(
                    background.convert(self.luma_device.mode))
Esempio n. 8
0
    def start(self):
        self.active = True
        colors = ["red", "orange", "yellow", "green", "blue", "magenta"]
        balls = [
            Ball(self.device.width, self.device.height, i * 1.5, colors[i % 6])
            for i in range(10)
        ]

        frame_count = 0
        fps = ""
        canvas = luma.core.render.canvas(self.device)

        regulator = framerate_regulator(fps=0)

        self.device.contrast(80)

        while self.active == True:
            with regulator:

                frame_count += 1
                with canvas as c:
                    #c.rectangle(self.device.bounding_box, outline="white", fill="black")
                    c.rectangle(self.device.bounding_box,
                                outline="black",
                                fill="black")
                    for b in balls:
                        b.update_pos()
                        b.draw(c)
        self.device.contrast(255)
Esempio n. 9
0
def show_message(device, msg, y_offset=0, fill=None, font=None, scroll_delay=0.03):
    """
    Scrolls a message right-to-left across the devices display.

    :param device: the device to scroll across
    :param msg: the text message to display (must be ASCII only)
    :type msg: str
    :param y_offset: the row to use to display the text
    :type y_offset: int
    :param fill: the fill color to use (standard Pillow color name or RGB tuple)
    :param font: the font (from :py:mod:`luma.core.legacy.font`) to use
    :param scroll_delay: the number of seconds to delay between scrolling
    :type scroll_delay: float
    """
    fps = 0 if scroll_delay == 0 else 1.0 / scroll_delay
    regulator = framerate_regulator(fps)
    font = font or DEFAULT_FONT
    with canvas(device) as draw:
        w, h = textsize(msg, font)

    x = device.width
    virtual = viewport(device, width=w + x + x, height=device.height)

    with canvas(virtual) as draw:
        text(draw, (x, y_offset), msg, font=font, fill=fill)

    i = 0
    while i <= w + x:
        with regulator:
            virtual.set_position((i, 0))
            i += 1
Esempio n. 10
0
    def bounce_message(device, msg, font=None, logo=None, delay=0):
        x_offset = 0 if logo is None else 8
        w = font.getsize(msg)[0]
        x = device.width

        regulator = framerate_regulator(20)
        virtual = viewport(device, width=w + x, height=device.height)
        virtual.logo = logo

        with canvas(virtual) as draw:
            draw.text((x_offset, 0), msg, fill='white', font=font)

        i = 0
        for _ in range(0, delay):
            with regulator:
                virtual.set_position((0, 0))

        while i <= w - x:
            with regulator:
                virtual.set_position((i, 0))
                i += 1

        for _ in range(0, delay):
            with regulator:
                virtual.set_position((i, 0))

        while i >= 0:
            with regulator:
                virtual.set_position((i, 0))
                i -= 1
 device.hide() def display_icon(code,i):
 global device
 font = make_font("fontawesome-webfont.ttf", device.height - 10)
 with canvas(device) as draw:
     w, h = draw.textsize(text=code, font=font)
     left = (device.width - w) / 2
     top = (device.height - h) / 2
     draw.text((left, top), text=code, font=font, fill="white")
     s = str(i) + ":"
     print(s)
     updateDisplay() def dispay_message( speed=1,msg='Hello World'):
 global device
 device.show()
 font_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'fonts', 'Volter__28Goldfish_29.ttf'))
 font = ImageFont.truetype(font_path,35)
 with canvas(device) as draw:
     draw.text((0, 20), msg, font=font, fill="white")
     updateDisplay() def main(num_iterations=sys.maxsize):
 global device
 port = 1
 bus = smbus.SMBus(port)
 apds = APDS9960(bus)
 GPIO.setmode(GPIO.BOARD)
 GPIO.setup(7, GPIO.IN)
 #GPIO.add_event_detect(7, GPIO.FALLING, callback = intH)
 apds.setProximityIntLowThreshold(50)
 print("Gesture Test")
 print("============")
 apds.enableGestureSensor()
 device = get_device()
 regulator = framerate_regulator(fps=1)
 button = Button(21)
 button.when_pressed = Showtime
 #draw.text((0, 0), "Hello World", font=font, fill=255)
 dispay_message(6,'Start')
 t = threading.Thread(target=displaytimeout)
 t.start()
 i = 0
 #for code in infinite_shuffle(codes):
 while True:
     with regulator:
         num_iterations -= 1
         if num_iterations == 0:
             break
         if apds.isGestureAvailable():
             motion = apds.readGesture()
             if motion == APDS9960_DIR_LEFT:
                 i = i+1
             if motion == APDS9960_DIR_RIGHT:
                 i = i-1
             if i < 0:
                 i = 6
             if i > 5:
                 i = 0
             code = menuicons[i]
             display_icon(code, i) if __name__ == "__main__":
 try:
     main()
 except KeyboardInterrupt:
     pass
Esempio n. 12
0
def main(num_iterations=sys.maxsize):
    colors = ["red", "orange", "yellow", "green", "blue", "magenta"]
    balls = [
        Ball(device.width, device.height, i * 1.5, colors[i % 6])
        for i in range(10)
    ]

    frame_count = 0
    fps = ""
    canvas = luma.core.render.canvas(device)

    regulator = framerate_regulator(fps=0)

    while num_iterations > 0:
        with regulator:
            num_iterations -= 1

            frame_count += 1
            with canvas as c:
                c.rectangle(device.bounding_box, outline="white", fill="black")
                for b in balls:
                    b.update_pos()
                    b.draw(c)
                c.text((2, 0), fps, fill="white")

            if frame_count % 20 == 0:
                fps = "FPS: {0:0.3f}".format(regulator.effective_FPS())
Esempio n. 13
0
    def ShowScanned(self):
        """Draws the screen showing the last scanned tag."""
        regulator = framerate_regulator(fps=30)
        beer = PIL.Image.open(self._scanned_gif_path)
        size = [min(*self.luma_device.size)] * 2
        posn = ((self.luma_device.width - size[0]) // 2,
                self.luma_device.height - size[1])
        msg = 'Cheers ' + self._last_scanned + '!'
        if self._last_scanned_character:
            msg += ' {0:s}L'.format(
                GetShortAmountOfBeer(
                    self._last_scanned_character.GetAmountDrunk() / 100.0))

        for gif_frame in PIL.ImageSequence.Iterator(beer):
            with regulator:
                background = PIL.Image.new('RGB', self.luma_device.size,
                                           'black')
                # Add a frame from the animation
                background.paste(
                    gif_frame.resize(size, resample=PIL.Image.LANCZOS), posn)

                # Add a text layer over the frame
                text_layer = PIL.ImageDraw.Draw(background)
                text_width, text_height = text_layer.textsize(msg)
                text_pos = ((self.luma_device.width - text_width) // 2,
                            self.luma_device.height - text_height)
                text_layer.text(text_pos,
                                msg, (255, 255, 255),
                                font=self._font)

                self.luma_device.display(
                    background.convert(self.luma_device.mode))
Esempio n. 14
0
def main():
    print("Testing dislay rendering performance")
    print("Press Ctrl-C to abort test\n")

    regulator = framerate_regulator(fps=0)  # Unlimited
    device = get_device()
    image = Image.new(device.mode, device.size)
    draw = ImageDraw.Draw(image)
    demo.primitives(device, draw)

    for i in range(5, 0, -1):
        sys.stdout.write("Starting in {0} seconds...\r".format(i))
        sys.stdout.flush()
        time.sleep(1)

    try:
        while True:
            with regulator:
                device.display(image)

            if regulator.called % 31 == 0:
                avg_fps = regulator.effective_FPS()
                avg_transit_time = regulator.average_transit_time()

                sys.stdout.write(
                    "#### iter = {0:6d}: render time = {1:.2f} ms, frame rate = {2:.2f} FPS\r"
                    .format(regulator.called, avg_transit_time, avg_fps))
                sys.stdout.flush()

    except KeyboardInterrupt:
        del image
Esempio n. 15
0
 def __init__(self):
     serial = i2c(port=1, address=0x3c)
     self.device = ssd1306(serial, height=32)
     self.device.persist = True
     self.font = self.make_font("arial.ttf", 11)
     print("OLEDbar.__init__> all set. Max bars = ", OLEDbar.bars)
     self.regulator = framerate_regulator(fps=60)
     self.readtime = []
 def display(self, text, num_frames=40):
     device = Device(serial_interface=Serial(), **args)
     canvas = luma.core.render.canvas(device)
     regulator = framerate_regulator(fps=0)
     while num_frames > 0:
         with regulator:
             with canvas as c:
                 c.text((2, 0), text, fill="white", font=piboto_font)
         num_frames -= 1
def test_init_unlimited():
    regulator = framerate_regulator(fps=0)
    before = time.monotonic()
    with regulator:
        pass
    after = time.monotonic()

    assert regulator.max_sleep_time == -1
    assert before <= regulator.start_time <= after
    assert regulator.called == 1
def test_sleep():
    regulator = framerate_regulator(fps=100.00)
    before = time.monotonic()
    for _ in range(200):
        with regulator:
            pass
    after = time.monotonic()

    assert regulator.called == 200
    assert after - before >= 2.0
 def display_image(self, image):
     num_frames = 40
     device = Device(serial_interface=Serial(), **args)
     canvas = luma.core.render.canvas(device, background=image)
     regulator = framerate_regulator(fps=0)
     while num_frames > 0:
         with regulator:
             with canvas as c:
                 pass
         num_frames -= 1
def test_init_30fps():
    regulator = framerate_regulator(fps=30)
    before = time.monotonic()
    with regulator:
        pass
    after = time.monotonic()

    assert regulator.max_sleep_time == 1 / 30.00
    assert before <= regulator.start_time <= after
    assert regulator.called == 1
Esempio n. 21
0
def main(num_iterations=sys.maxsize):

    regulator = framerate_regulator(fps=30)

    vertices = [
        point(-1, 1, -1),
        point(1, 1, -1),
        point(1, -1, -1),
        point(-1, -1, -1),
        point(-1, 1, 1),
        point(1, 1, 1),
        point(1, -1, 1),
        point(-1, -1, 1)
    ]

    faces = [
        ((0, 1, 2, 3), "red"),
        ((1, 5, 6, 2), "green"),
        ((0, 4, 5, 1), "blue"),
        ((5, 4, 7, 6), "magenta"),
        ((4, 0, 3, 7), "yellow"),
        ((3, 2, 6, 7), "cyan")
    ]

    a, b, c = 0, 0, 0

    for angle, dist in sine_wave(8, 40, 1.5):
        with regulator:
            num_iterations -= 1
            if num_iterations == 0:
                break

            t = [v.rotate_x(a).rotate_y(b).rotate_z(c).project(device.size, 256, dist)
                for v in vertices]

            depth = []
            for idx, face in enumerate(faces):
                v1, v2, v3, v4 = face[0]
                avg_z = (t[v1].z + t[v2].z + t[v3].z + t[v4].z) / 4.0
                depth.append((idx, avg_z))

            with canvas(device, dither=True) as draw:
                for idx, depth in sorted(depth, key=itemgetter(1), reverse=True)[3:]:
                    (v1, v2, v3, v4), color = faces[idx]

                    if angle // 720 % 2 == 0:
                        fill, outline = color, color
                    else:
                        fill, outline = "black", "white"

                    draw.polygon(t[v1].xy + t[v2].xy + t[v3].xy + t[v4].xy, fill, outline)

            a += 0.3
            b -= 1.1
            c += 0.85
Esempio n. 22
0
def initiate_gif(img_path):
    regulator = framerate_regulator(fps=10)
    banana = Image.open(img_path)
    count = 0

    while count <= 10:
        for frame in ImageSequence.Iterator(banana):
            count += 2
            with regulator:
                background = Image.new("RGB", device.size, "white")
                background.paste(frame)
                device.display(background.convert(device.mode))
def test_init_default():
    regulator = framerate_regulator()
    assert regulator.start_time is None
    assert regulator.last_time is None
    assert regulator.called == 0
    before = time.monotonic()
    with regulator:
        pass
    after = time.monotonic()

    assert regulator.max_sleep_time == 1 / 16.67
    assert before <= regulator.start_time <= after
    assert regulator.called == 1
Esempio n. 24
0
    def scroll_message(device, message, font):
        regulator = framerate_regulator(25)
        x = device.width
        w = font.getsize(message)[0]
        virtual = viewport(device, width=w + x + x, height=device.height)
        with canvas(virtual) as draw:
            draw.text((x, 0), message, fill='white', font=font)

        i = 0
        while i <= w + x:
            with regulator:
                virtual.set_position((i, 0))
                i += 1
Esempio n. 25
0
 def __init__(self):
     self._serial = spi()
     self._device = ssd1322(self._serial, mode="1", rotate=2)
     self._fonts = makeFonts()
     self._color = "yellow"  # hardcoded color as it's useless on monochrome screen
     self._WIDTH = 256
     self._HEIGHT = 64
     self._STATUS_APPROACHING = "Train a l'approche"
     self._STATUS_DELAYED = "Train retarde"
     self._STATUS_TERMINATED = "Service termine"
     self._STATUS_PP = "++   "
     self._STATUS_MM = "00 mn"
     self._STATUS_NODATA = "Pas de train a"
     self.regulator = framerate_regulator(fps=10)
Esempio n. 26
0
def main():
    regulator = framerate_regulator(fps=10)
    img_path = os.path.abspath(
        os.path.join(os.path.dirname(__file__), 'images', 'banana.gif'))
    banana = Image.open(img_path)
    size = [min(*device.size)] * 2
    posn = ((device.width - size[0]) // 2, device.height - size[1])

    while True:
        for frame in ImageSequence.Iterator(banana):
            with regulator:
                background = Image.new("RGB", device.size, "white")
                background.paste(frame.resize(size, resample=Image.LANCZOS),
                                 posn)
                device.display(background.convert(device.mode))
Esempio n. 27
0
def main():
    regulator = framerate_regulator(fps=25)
    img_path = os.path.abspath(os.path.join(os.path.dirname(__file__),
        'images', 'cardano-loader_128x128.gif'))
    agif = Image.open(img_path)
    size = [min(*device.size)] * 2
    posn = ((device.width - size[0]) // 2, device.height - size[1])

    counter = 0
    while counter < 3:
        counter +=1
        for frame in ImageSequence.Iterator(agif):
            with regulator:
                background = Image.new("RGB", device.size, "black")
                background.paste(frame.resize(size, resample=Image.LANCZOS), posn)
                device.display(background.convert(device.mode))
Esempio n. 28
0
 def _DisplayGIFOnce(self, image_path):
     ''' 显示GIF动图 '''
     self.next.clear()
     serial = i2c(port=1, address=0x3c)
     device = ssd1306(serial)  #挂载设备
     regulator = framerate_regulator(fps=30)
     ImageFile = Image.open(image_path)  # Open a picture
     size = [min(*device.size)] * 2
     posn = ((device.width - size[0]) // 2, device.height - size[1])
     for frame in ImageSequence.Iterator(ImageFile):
         with regulator:
             background = Image.new('RGB', device.size, 'black')
             background.paste(frame.resize(size, resample=Image.LANCZOS),
                              posn)
             background = background.convert(device.mode)
             device.display(background)
def main(num_iterations=sys.maxsize):
    device = get_device()
    regulator = framerate_regulator(fps=1)
    font = make_font("fontawesome-webfont.ttf", device.height - 10)

    for code in infinite_shuffle(codes):
        with regulator:
            num_iterations -= 1
            if num_iterations == 0:
                break

            with canvas(device) as draw:
                w, h = draw.textsize(text=code, font=font)
                left = (device.width - w) / 2
                top = (device.height - h) / 2
                draw.text((left, top), text=code, font=font, fill="white")
Esempio n. 30
0
def main(num_iterations=sys.maxsize):
    data = {
        'image': os.path.abspath(
            os.path.join(
                os.path.dirname(__file__),
                'images', 'runner.png')),
        'frames': {
            'width': 64,
            'height': 67,
            'regX': 0,
            'regY': 2
        },
        'animations': {
            'run-right': {
                'frames': range(19, 9, -1),
                'next': "run-right",
            },
            'run-left': {
                'frames': range(0, 10),
                'next': "run-left"
            }
        }
    }

    regulator = framerate_regulator()
    sheet = spritesheet(**data)
    runner = sheet.animate('run-right')
    x = -sheet.frames.width
    dx = 3

    while num_iterations > 0:
        with regulator:
            num_iterations -= 1

            background = Image.new(device.mode, device.size, "white")
            background.paste(next(runner), (x, 0))
            device.display(background)
            x += dx

            if x >= device.width:
                runner = sheet.animate('run-left')
                dx = -dx

            if x <= -sheet.frames.width:
                runner = sheet.animate('run-right')
                dx = -dx