Micropython gragraphis guide by example

Not sure why but many of the comments delineated by a single # ended up in H1 font.

I replace it with // for comments. Some of them were unaffected.

Here’s a bit of a howto for graphics in micropython. I’ve created this because finding out this information was a trip to numerous and sometimes almost cryptic places. If such a guide has already been created, I apologize, but finding it was beyond my grasp.

First the background. I’m new to the picocalc and to micropython (or any python), so I wanted to learn each. It may be something that interests me but doing graphics is one of the tests I like to do with a new system and/or language.

I started by using mmbasic and created a box that takes up most of the display and had three balls moving in the box. As a ball hit one of the walls, it changed it’s motion by reflection off the wall. The wall that was hit briefly changes color.

Next I built the equivalent program in micropython. Unlike mmbasic, there were no direct commands for doing graphics. The closest was to import display from the drivers included in the picocalc version of micropython. It turns out that the display imported that way acts like a framebuffer. The various framebuffer commands work on it.

Since the box was essentially four long and thin rectangles, the display.rect() command worked to create the walls. The balls were made from the display.ellipse() command. I’ll add details of using these commands later.

Well, much to my surprise when I used these commands directly in micropython, the speed at which the equivalent program ran was just about the same or even a little slower. An independent test of just the calculation speed of micropython vs mmbasic was to use the series for calculation Euler’s number to 1000 places. In mmbasic it took a bit over 1.6sec to do the calculation. In micropython, the calculation took 6 millisecons. Micropython is substantially faster.
What that told me was that the bottle neck for this was the interface to the display. It was slower than either programming language.

Thank to an entry by user tabemann on a post I had made about this, I switched from working directly with the display to working with a framebuffer and updating the display after the much faster programming had finished its processing. It was a bit of a different approach and had its own tweaks but in the end, it worked and was much faster than what mmbasic provided.

The rest of this is a breakdown of the finished product with hopefully enough commentary and examples to inform any else of how to do the graphics.

Micropython lets you save on the utilization of memory by letting you
load only extra functions that you need.
This instance of micropython uses drivers included in the release called
picocalc (make is easy to remember!) The functions from picocalc deal
with how micropython talks to the hardware. Their names are straight
forward.
#======================================================================

//initialization

from picocalc import display, keyboard, terminal

#======================================================================

//framebuf contains a construct that holds video information

FrameBuffer. It requires a memory location to store data

import framebuf

#======================================================================
import random # a way to create random numbers
import time # time functions such as sleep()

#=====================================================================

#=========================================================

//define parameters used in the program

//assign names to colors

black = 0
red = 1
green = 2
yellow = 3

/framebuffer actions (draw/erase)

bdrw = 0 # draw
bers = 1 # erase

//Short name for display

fb = display # much easier to type

//create framebuffer memory arrays

//the size of the bytearray is the size of the area of the display

//the size of the bytearray is the size of area of the display you are going to use then times

//2 for the color information

//Memory location used by the framebuffers

winbuf = bytearray(5 * 280 * 2) # for the walls of the box
wincir = bytearray(5 * 5 * 2) # for the balls

//temp array for keyboard entry

temp = bytearray(1) # used when I’m waiting for a keypress

to end the program.

//here are the various frambuffers I used

//framebuffers (bytearray, width, height, colors)

//framebuf.RGB565 allows a blend of Red, Green, and Blue to

//make 16 colors (numbered 0 to 15. I’m using 4 as noted above.

//horizontal framebuffer - bottom and top walls of the box

hfb = framebuf.FrameBuffer(winbuf, 280, 5, framebuf.RGB565)

verticle framebuffer - left and right walls of the box

vfb = framebuf.FrameBuffer(winbuf, 5, 280, framebuf.RGB565)

//ball framebuffer - the ball

cfb = framebuf.FrameBuffer(wincir, 5, 5, framebuf.RGB565)

//end of parameter setup There will be a few local variables

//if I remember correctly but shouldn’t be a problem

==========================================================

//Since programs don’t really have a true random number,

//I try to mix it up a bit. Not really necessary.

//initialize random number

random.seed(23)
lpy = int(100 * random.random())
i = 0
while i < lpy:
tt = random.random()
i += 1
daseed = int(1000 * random.random())
random.seed(daseed)

#==========================================================================

//functions the first are the ones using the frame buffers

//draw a ball. Because I need to erase the ball where it is before

//create the ball in it’s new location as it moves, I found that

//I needed to have an option to erase (make invisible?) the existing

//ball by using the action parameter.

def drawball(xe, ye, color, action):
cfb.ellipse(1, 1, 5, 5, color, True) # location/size in framebuffer

in framebuffer cfb remember it was a 5 by 5 location. I’m creating

an ellipse the resides in that framebuffer taking up all its space.

the True parameter tells it fill in the object with the color

instead of just doing an outline

That’s location 1,1 and it’s major and minor axis are each 5.

fb.blit(cfb, xe, ye, action) # location in larger framebuffer
# fb is the shortcut to the actual device display.  This command
# takes the created framebuffer and copies it onto the display at
# display location given by xe and ye.  The action either draws it
# or erases it from the display. 

def drawtop(color, action):
hfb.rect(1, 1, 280, 5, color, True)

a rectangle 280 pixels wide and 5 pixels thick

fb.blit(hfb, 20, 20, action)

def drawbottom(color, action):
hfb.rect(1, 1, 200, 5, color, True)
fb.blit(hfb, 20, 295, action)

the bottom uses the same setup except the location where it

is added to the display

def drawleft(color, action):
vfb.rect(1, 1, 5, 280, color, True)
fb.blit(vfb, 20, 20, action)

now it’s the vertical walls. Note: it’s 5 wide and 280 tall

def drawright(color, action):
vfb.rect(1, 1, 5, 280, color, True)
fb.blit(vfb, 295, 20, action)

added to the display at it own location

#======================================================================

//now for functions I needed to build the program

//I use the random function and a few if statements to start

//the balls out in locations that were inside the box and away

//from the walls

def getpos():
i = int(320 * random.random())

# start away from box walls
tst = True
while tst is True:
    if i < 25 or i > 295:
        i = int(320 * random.random())
    else:
        break
else:
    tst = False
return(i)

// x,y direction - set which way the balls are moving at start

def updn():
drec = int(100 * random.random() + 0.5)
if drec < 50:
return(0)
else:
return(1)

//this function and it y direction equivalent caused the most trouble

//aside from just not knowing what to do.

//I found that micropython calculated things so fast that the attempt to

//flash the walls when a ball hit them was checking, changing,

//changing back multible times before the ball would leave the area

//of the wall. The extra variable, xhcnt was added so I could keep

//the wall flashed until the ball moved out of the detection range.

//check for wall hit in x direction

def chkx(xf, bxdf, xhcnt):
if xf < 30:
bxdf = 0
if xf > 290:
bxdf = 1
if bxdf == 0:
xf = xf + 2 + int(0.8 * random.random()+0.5)
else:
xf = xf - 2 - int(0.8 * random.random()+0.5)

//I added the small random value to allow the balls to move in a path that wasn’t exactly straight. //That way the ball would eventuall move all over the box.

# flash wall color if hit
if xf < 40:
    if xhcnt == 0:
        drawleft(red, bdrw)
        time.sleep(0.02)
        xhcnt = 1
#    drawleft(green, bdrw)
elif xf > 285:
    if xhcnt == 0:
        drawright(red, bdrw)
        time.sleep(0.02)
        xhcnt = 1
#    drawright(green, bdrw)
else:
    if xhcnt > 0:
        drawleft(green, bdrw)
        drawright(green, bdrw)
        xhcnt = 0

return(xf, bxdf, xhcnt)
# xhcnt is returned to the calling routing to keep track of where
# the ball is.

//duplicate of the chkx code for the y direction

//check for wall hit in y direction

def chky(yf, bydf, yhcnt):
if yf < 30:
bydf = 0
if yf > 290:
bydf = 1
if bydf == 0:
yf = yf + 2 + int(0.8 * random.random()+0.5)
else:
yf = yf - 2 - int(0.8 * random.random()+0.5)

# flash wall if hit
if yf < 40:
    if yhcnt == 0:
        drawtop(red, bdrw)
        time.sleep(0.02)
        yhcnt = 1
#    drawtop(green, bdrw)
elif yf > 285:
    if yhcnt == 0:
        drawbottom(red, bdrw)
        time.sleep(0.02)
        yhcnt = 1
else:
    if yhcnt > 0:
        drawtop(green, bdrw)
        drawbottom(green, bdrw)
        yhcnt = 0        
#    drawbottom(green, bdrw)
return(yf, bydf, yhcnt)

//the wall starts out green and flashes red when hit

//then turns back green

#================End of defines ===============================

#===============Initialize box and balls ======================

//clear the framebuffers. clears the display

fb.fill(black)
hfb.fill(black)
vfb.fill(black)
cfb.fill(black)

//draw ball 1

x1 = getpos() # random x location inside and away from box wall
y1 = getpos() # random y location inside and away from box wall
b1xd = updn() # random choice of left or right for x
b1yd = updn() # random choice of up or down for y
drawball(x1, y1, yellow, bdrw) # draw the ball in yellow

//repeated for two more balls

//draw ball 2

x2 = getpos()
y2 = getpos()
b2xd = updn()
b2yd = updn()
drawball(x2, y2, yellow, bdrw)

//draw ball 3

x3 = getpos()
y3 = getpos()
b3xd = updn()
b3yd = updn()
drawball(x3, y3, yellow, bdrw)

#========================================================================

//draw the box walls - bdrw action to draw

//top border

color = green
drawtop(color, bdrw)

//bottom border

color = green
drawbottom(color, bdrw)

//left border

color = green
drawleft(color, bdrw)

//right border

color = green
drawright(color, bdrw)

//end of drawing the box

==============End of initialization ==============================

==========Move balls ======================================

//ok, maybe should be in the initializatin

hide cursor

terminal.wr(“\x1b[?25l”)

//zero wall hits. balls start away from walls no hits

x1hcnt = 0 # and x and y hit count for each ball.
x2hcnt = 0 # actually it’s a hit or no hit instead
x3hcnt = 0 # of a count.
y1hcnt = 0
y2hcnt = 0
y3hcnt = 0

while True:
drawball(x1, y1, black, bers) # erase old ball location
x1, b1xd, x1hcnt = chkx(x1, b1xd, x1hcnt)
y1, b1yd, y1hcnt = chky(y1, b1yd, y1hcnt)
drawball(x1, y1, yellow, bdrw) # draw ball at new location

# same for the other two balls
drawball(x2, y2, black, bers)
x2, b2xd, x2hcnt = chkx(x2, b2xd, x2hcnt)
y2, b2yd, y2hcnt = chky(y2, b2yd, y2hcnt)
drawball(x2, y2, yellow, bdrw)

drawball(x3, y3, black, bers)
x3, b3xd, x3hcnt = chkx(x3, b3xd, x3hcnt)
y3, b3yd, y3hcnt = chky(y3, b3yd, y3hcnt)
drawball(x3, y3, yellow, bdrw)

# short pause to slow it down a bit.
time.sleep(0.01)

# keep looping until a key is pressed
if keyboard.readinto(temp):
    break

//end game

//clear terminal buffer and move cursor to top

terminal.wr(“\x1b[2J\x1b[H”)

//show cursor

terminal.wr(“\x1b[?25h”)

#==================================================================
#==================end of program=================================

Summary of graphic commands:

fb.fill(0)

//fill the display or framebuffer (0 is black)

fb.rect(x-loc, y-loc, width, height, color, True)

//build a rectangle in the framebuffer (fb) or the display

//use True to fill with color

fb.ellipse(x-loc, y-loc, x-radius, y-radius, color, True)

//build an ellipse in the framebuffer (fb or the display)

//use True to fill with color

fb.text(str$, x-loc, y-loc, color)

//writes text to the framebuffer or display

display.blit(fb, x-loc, y-loc, key)

//puts fb onto display (or another framebuffer if desired)

//key allows visibility of new overlay if needed.

//I’m still a bit confused about its use but it was needed

//to erase the old ball.

//More information about framebuffer use can be found at

1 Like

UGH! So many typo’s and such. Again, apologies.