Help with Circuitpython Graphics

I know I must be mad but I’m trying to use Circuitpython graphics. (Why on earth did Adafruit make their graphics system so complicated? Picographics are much easier to understand, use and produce excellent results.)

I’m currently using a CLUE:
Adafruit CircuitPython 9.2.4 on 2025-01-28; Adafruit CLUE nRF52840 Express with nRF52840

Here is my code:

# Circuitpython vectorio test
# Tony Goodhew 2 Feb 2025
# I'm using a CLUE with 240x 240 display
import time
import board
import displayio
import vectorio

display = board.DISPLAY # get display object (built-in on some boards)
maingroup = displayio.Group()
display.root_group = maingroup

palette = displayio.Palette(1)
palette[0] = 0xff0000

circle = vectorio.Circle(pixel_shader=palette, radius=25, x=100, y=200)
maingroup.append(circle)

rectangle = vectorio.Rectangle(pixel_shader=palette, width=40, height=45, x=155, y=45)
maingroup.append(rectangle)

points=[(5, 5), (100, 20),  (20, 100)]
triangle = vectorio.Polygon(pixel_shader=palette, points=points, x=0, y=0)
maingroup.append(triangle)

while True:
    pass

How can I get the three shapes to have different colours? I would like RED,GREEN and BLUE.

I feel your pain. A while back I bought a Matrix Portal to use with some LED Matrix Panels. I found it so exceedingly frustrating trying to use Adafruits graphics offering that I eventually just threw in the towel and bought an Interstate 75.

My current project requires a high pixel count round display and I need to be able to draw a black circle over a series of 60 narrow radial triangles to leave a ring of thick ‘seconds ticks’ round the edge of the screen. Unfortunately, Pimoroni do not supply a round RGB666 display and driver board. The only one I can find is the Adafruit one. Their documentation of the over complicated displayio and vectorio graphics system leaves too much unexplained and there are no complete and working code examples to use as a starting point. I am not interested in “1980’s games” running small sprites over complicated backgrounds when I push buttons. I would rather build interesting dials, meters and graphs which react to inputs from sensors and data from the internet.

I have the whole project working perfectly on a wonderful Presto - but it is square and the corners stick out too much.

I’ve just found draw-shapes - I will give that a go.

This works but the speed is glacial.

import board
import displayio
from adafruit_display_shapes.rect import Rect
from adafruit_display_shapes.circle import Circle
#from adafruit_display_shapes.roundrect import RoundRect
from adafruit_display_shapes.triangle import Triangle
#from adafruit_display_shapes.line import Line
#from adafruit_display_shapes.polygon import Polygon

# Make the display context
splash = displayio.Group()
board.DISPLAY.root_group = splash

# Make a background color fill
color_bitmap = displayio.Bitmap(240, 240, 1)
color_palette = displayio.Palette(1)
color_palette[0] = 0x000000
bg_sprite = displayio.TileGrid(color_bitmap, x=0, y=0, pixel_shader=color_palette)
splash.append(bg_sprite)
##########################################################################

triangle = Triangle(119, 119, 115, 1, 125, 1, fill=0x00FF00)
splash.append(triangle)

triangle = Triangle(119, 119, 1, 115, 1, 125, fill=0x00FF00)
splash.append(triangle)


circle = Circle(119, 119, 90, fill=0x000000)
splash.append(circle)

while True:
    pass

Picographics can do two rings of 60 ticks and two block out the centre circles in half a second!

That’s how it went for me also. I hope you can figure it out, I didn’t have the patience to see it through.

I’ve asked on the Adafruit Forum for some help. We will see how that gets on. I hope to sort it out once and for all.

1 Like

How did you configure your SPI-bus? This makes a huge difference.

And btw, CircuitPython has a huge base of learning-guides with ready to use examples for displayio, including many real life examples. In addition, every part of displayio (drivers, higher level widgets and so on) has an example-subdirectory in the repository with a number of examples, from absolute basic to more complex.

Also keep in mind: PicoGraphics is implemented in C for a extremely limited set of displays all from Pimoroni. So they can optimize everything with the caveat that you cannot use this software for 99.99% of the screens on the market.

In addition, the graphics system of CircuitPython is fully portable. You can take your code and just run it on a different processor with a potentially different screen.

Like always in IT, there are competing goals. Best performance is generally not possible with generic, portable solutions.

Some other notes: do you know the Clue learning guide? Overview | Introducing Adafruit CLUE | Adafruit Learning System It also has a number of CircuitPython examples (“complete and working code”). Other guides, e.g. guides specific to Displayio, fonts, UI-helpers and so on can be found on https://learn.adafruit.com

Regarding the speed: the NRF of the Clue is a 64MHz processor. The Pico you are used to has two 133 MHz processors. The new Pico2 even has up to 150 MHz. And since this are different CPU generations, the speed differences are even higher. So yes, your Clue will always be slow compared to the Pico.

So you should think about some changes in your code: first thing I would do is turn auto-refresh off. Depending on what you do, an explicit refresh at the right time saves a lot of processing. I would also replace the tight loop at the end of your program. Instead of just looping you could add a tiny amount of sleep to give background tasks a chance to do their processing.

I would also get rid of the black background, at least for this example. And I would stick with vectorio when using filled shapes. vectorio is implemented in C, while display_shapes is implemented in Python. Your very first example should be fine, but you should use a different palette for every shape if you want different colors. Or use a palette with all your colors and pass the appropriate color-index to the shapes.

Thank you for your help.

I am using the latest version of the CLUE uf2 and bootloader - so I hope the SPI has been set up in the most efficient way.

I realise that a Pico has a faster processor but this CLUE is the most recent Adafruit board/display combo I have at the moment and so will be slower than a Pico board.

This uses vectorio, implemented in C, so is faster than display_shapes.

# Circuitpython vectorio test
# Tony Goodhew 3rd Feb 2025
# I'm using a CLUE with 240x240 display
import time
import board
import displayio
import vectorio

display = board.DISPLAY # get display object (built-in on some boards)
maingroup = displayio.Group()
display.root_group = maingroup

pal = displayio.Palette(4)

pal[0] = 0xff0000  # RED
pal[1] = 0x00ff00  # GREEN
pal[2] = 0x0000ff  # BLUE
pal[3] = 0x000000  # BLACK

display.auto_refresh=False

points=[(5, 5), (0, 200),  (200, 0)]
triangle = vectorio.Polygon(pixel_shader=pal, points=points, x=0, y=0)
maingroup.append(triangle)

circle = vectorio.Circle(pixel_shader=pal, radius=50, x=100, y=90)
maingroup.append(circle)

rectangle = vectorio.Rectangle(pixel_shader=pal, width=60, height=45, x=155, y=15)
maingroup.append(rectangle)

display.auto_refresh=True

while True:
    pass

I’ve put some colours in the palette but cannot see how to use the palette to turn the circle BLACK and the rectangle BLUE.

You also have to tell the shape which color to use. All these object constructors take a color_index argument (see vectorio – Lightweight 2D shapes for displays — Adafruit CircuitPython 9.2.4 documentation).

SPI is hardwired at 60_000_000, so this should be fine for the Clue. You should use display.refresh() and keep auto_refresh = False. Doesn’t matter for this example, but eventually you will start reading the sensors and updating some text on the screen. A final refresh after all the updates is much cleaner visually IMHO.

1 Like

I missed the color_index = 3 or whatever. That works thanks.

# Circuitpython vectorio test
# Tony Goodhew 3rd Feb 2025
# I'm using a CLUE with 240x240 display
import time
import board
import displayio
import vectorio

display = board.DISPLAY # get display object (built-in on some boards)
maingroup = displayio.Group()
display.root_group = maingroup

pal = displayio.Palette(4)

pal[0] = 0xff0000  # RED
pal[1] = 0x00ff00  # GREEN
pal[2] = 0x0000ff  # BLUE
pal[3] = 0x000000  # BLACK

display.auto_refresh=False

points=[(5, 5), (0, 200),  (200, 0)]
triangle = vectorio.Polygon(pixel_shader=pal, points=points, x=0, y=0,color_index=0)
maingroup.append(triangle)

circle = vectorio.Circle(pixel_shader=pal, radius=50, x=100, y=90,color_index=3)
maingroup.append(circle)

rectangle = vectorio.Rectangle(pixel_shader=pal, width=60, height=45, x=155, y=15,color_index=2)
maingroup.append(rectangle)

display.refresh()
time.sleep(0.2)

while True:
    pass

Working now - Thanks

1 Like