@SteveA I think we’re all learning together!
Putting RGB into the buffer should just be a matter of writing it there PROVIDED it has the same bits/pixel as you’ve set for the display and the same resolution as you desire (WIDTH X HEIGHT for full display, or for a smaller area).
Otherwise you should convert the image to match what you want on the screen.
@EperiMentor, yes, progress step by step. As I mentioned in my first posts, I wrote a PC program to convert BMP to rgb_888/666/565/332 files which I used in my earlier PIC and OLED sensor projects. I saw the below command in an internet article which loads in a raw rgb file (faster than byte by byte copy from a disk file) and wonder if a similar on is hidden in Pimoroni’s UF2. I see that they have a jpg load.
oled.blit(fb,32,0) # THIS IS GETTING FILE DATA onto display at x, y
Some progress.
I’ve got a method of saving the screen buffer to a file and restoring it from the file. The program is here:
# Grab and replace a screen section
from picographics import PicoGraphics, DISPLAY_LCD_240X240,PEN_RGB332
import math
import time
display = PicoGraphics(display=DISPLAY_LCD_240X240, rotate=0, pen_type=PEN_RGB332)
WIDTH, HEIGHT = display.get_bounds() # gets display size, expecting 240, 240
print(WIDTH,HEIGHT)
# Assign own framebuffer so we can read from it (to be able to restore colours under cursor when it is moved)
display.set_framebuffer(None) # not sure if this is needed
buffer = bytearray(int(WIDTH*HEIGHT))
display.set_framebuffer(buffer)
red = display.create_pen(255,0,0)
white = display.create_pen(255,255,255)
green = display.create_pen(0,255,0)
blue = display.create_pen(0,0,255)
black = display.create_pen(0,0,0)
yellow = display.create_pen(255,255,0)
cyan = display.create_pen(0,255,255)
magenta = display.create_pen(255,0,255)
def grab(left, top, w, h): # Grab a rectangle of screen
i = 0
for y in range(top,top + h):
for x in range(w):
grabbed[i] = buffer[left + x -1 + (y * WIDTH)]
i = i+1
def replace(left, top, w, h): # Replace the save screen rectangle
i = 0
for y in range(top,top + h):
for x in range(w):
buffer[left + x -1 + (y * WIDTH)] = grabbed[i]
i = i+1
# =========== Main Program ================
display.set_font("bitmap8") # Provides Lower Case letters
display.set_pen(black)
display.clear()
# Create screen background
xc = 160
yc = 160
display.set_pen(blue)
display.circle(xc,yc,160)
display.set_pen(red)
display.circle(xc,yc,140)
display.set_pen(yellow)
display.circle(xc,yc,120)
display.set_pen(green)
display.text("MPH",2,115,scale=3)
display.update()
time.sleep(1)
# Screen dump binary file
f = open("data", "wb")
f.write(buffer)
f.close()
print("File written")
# Damage screen display
display.set_pen(black)
display.rectangle(0,0,200,200)
display.update()
time.sleep(1)
# Re-draw the background
with open("data", "rb") as g:
for y in range(240):
line = g.read(240)
for x in range(240):
byte = line[x]
buffer[x -1 + (y * WIDTH)] = byte
display.update()
All you need to do is output your converted screen in 332 format to a file in the same format as the buffer.
Blit copies a small buffer covering an area of the screen to the main buffer. It does not use the filing system.
FrameBuffer.blit(fbuf, x, y, key=- 1, palette=None )
[¶(framebuf — frame buffer manipulation — MicroPython latest documentation)
Draw another FrameBuffer on top of the current one at the given coordinates. If key is specified then it should be a color integer and the corresponding color will be considered transparent: all pixels with that color value will not be drawn. (If the palette is specified then the key is compared to the value from palette, not to the value directly from fbuf.)
The palette argument enables blitting between FrameBuffers with differing formats. Typical usage is to render a monochrome or grayscale glyph/icon to a color display. The palette is a FrameBuffer instance whose format is that of the current FrameBuffer. The palette height is one pixel and its pixel width is the number of colors in the source FrameBuffer. The palette for an N-bit source needs 2**N pixels; the palette for a monochrome source would have 2 pixels representing background and foreground colors. The application assigns a color to each pixel in the palette. The color of the current pixel will be that of that palette pixel whose x position is the color of the corresponding source pixel.
Thanks to the code I have learnt and picked up from everyone, I have been optimising the screen area grab and restore to/from the framebuffer and the code is below to run on a 320x240 Display.
Test 1, 2 and 3 work fine and is optimised as best I can to read the data in chunks from a temporary memory array or disk file and not read byte by byte, which makes it about 4 x faster.
Q: do you know of any way to improve the optimisation?
I have tried to read in the whole file in 1 pass when I did test 2, using
f = open(FULLSCREENFILENAME, “rb”)
framebuffer = f.read()
f.close()
Framebuffer has already been created and used and all I wanted to do is load in the disk file (which is the same size) back into the framebuffer array. I get a memory allocation error when I try to do it in 1 pass and I am not sure of the syntax. I have tried various ways to load the disk file in 1 pass into the already created framebuffer array, eg
framebuffer[0:] = f.read()
framebuffer[0:] = f.read(-1)
but I still get the memory allocation error
Q2: do you know how to load in the complete disk file into the framebuffer array in 1 pass to optimise speed?
# Grab and replace a screen section - a series of tests, SteveA
# framebuff-mytest1.py
from picographics import PicoGraphics, DISPLAY_PICO_DISPLAY_2,PEN_RGB332
import time
# import math
from pimoroni import RGBLED
led = RGBLED(6, 7, 8) # LED pins, RGBLED(6, 7, 8, invert=True) invert reverse polarity, not working
led.set_rgb(20, 0, 0) # RGB colour 0..255, set red to start
import micropython
import gc # https://docs.micropython.org/en/latest/library/gc.html
# useful funcs:
# gc.collect() # free memory, needs import gc
# del(element) # delete element, variable, list, or array
# raise SystemExit(0) # safe exit
# FUNCTIONS ===
def memreport(where):
x12 = gc.mem_alloc() # how much allocated
y12 = gc.mem_free() # how much free
print("Allocated & Free memory at '", where, "' in code = ",x12, y12)
def fullmemreport():
gc.collect()
micropython.mem_info()
print('-----------------------------')
print('Initial free: {} allocated: {}'.format(gc.mem_free(), gc.mem_alloc()))
def func():
a = bytearray(100) # 10000
gc.collect()
print('Func definition: {} allocated: {}'.format(gc.mem_free(), gc.mem_alloc()))
func()
print('Func run free: {} allocated: {}'.format(gc.mem_free(), gc.mem_alloc()))
gc.collect()
print('Garbage collect free: {} allocated: {}'.format(gc.mem_free(), gc.mem_alloc()))
print('-----------------------------')
def clearrect(x0, y0, x1, y1): # Clear a rectangle of screen
display.set_pen(black)
display.rectangle(x0,y0,x1-x0+1,y1-y0+1) # x,y,w,h
display.update()
# create a small mem buf to save restore
grabbedbuf = bytearray(int(200*200))
def grabscreenrect(x0, y0, x1, y1): # Grab a rectangle of screen byte by byte
gbpos = 0
linedatalen = x1 - x0 + 1 # len of data needed on that line
for y in range(y0, y1+1): # do each line
fbpos = x0 + (y * SCREENWIDTH) # x0 + num bytes per line
grabbedbuf[gbpos:gbpos+linedatalen] = framebuffer[fbpos:fbpos+linedatalen]
gbpos = gbpos + linedatalen
def restorescreenrect(x0, y0, x1, y1): # Replace the save screen rectangle
gbpos = 0
linedatalen = x1 - x0 + 1 # len of data needed on that line
for y in range(y0, y1+1):
fbpos = x0 + (y * SCREENWIDTH) # x0 + num bytes per line
framebuffer[fbpos:fbpos+linedatalen] = grabbedbuf[gbpos:gbpos+linedatalen]
gbpos = gbpos + linedatalen
# INIT VARS ===
fullmemreport()
display = PicoGraphics(display=DISPLAY_PICO_DISPLAY_2, rotate=0, pen_type=PEN_RGB332)
SCREENWIDTH, SCREENHEIGHT = display.get_bounds() # gets display size, expecting 240, 240
print("Screen w and h:", SCREENWIDTH, SCREENHEIGHT)
display.set_backlight(1.0)
# Assign own framebuffer so we can read from it (to be able to restore colours under cursor when it is moved)
display.set_framebuffer(None) # Turn off Pimoroni's framebuffer, not sure if this is needed
FULLSCREENNUMBYTES = SCREENWIDTH * SCREENHEIGHT
framebuffer = bytearray(int(FULLSCREENNUMBYTES)) # create mem array for single rgb332 bytes to match screen
display.set_framebuffer(framebuffer) # and set as my frame buffer
memreport("framebuf creation")
# define useful colours
red = display.create_pen(255,0,0)
white = display.create_pen(255,255,255)
green = display.create_pen(0,255,0)
blue = display.create_pen(0,0,255)
black = display.create_pen(0,0,0)
yellow = display.create_pen(255,255,0)
cyan = display.create_pen(0,255,255)
magenta = display.create_pen(255,0,255)
FULLSCREENFILENAME = "fullscreendata" # datafile
display.set_font("bitmap8") # Provides Lower Case letters
# clear screen and start tests
display.set_pen(black)
display.clear()
# MAIN Program ===
# Create simple screen background from TG
xc = 160
yc = 120
display.set_pen(blue)
display.circle(xc,yc,160)
display.set_pen(red)
display.circle(xc,yc,140)
display.set_pen(yellow)
display.circle(xc,yc,120)
display.set_pen(green)
display.text("---TEXT---",2,115,scale=4)
display.update()
time.sleep(1)
# set screen display area to damage each time
x0 = 40
y0 = 50
x1 = 199
y1 = 199
print("TEST 1 - grab rect section of screen into mem buffer")
led.set_rgb(0, 20, 0)
grabscreenrect(x0,y0,x1,y1) # save framebuffer pixels to match size
clearrect(x0,y0,x1,y1)
restorescreenrect (x0, y0, x1, y1) # restore framebuffer pixels to match size
display.update()
print("Restored screen rect from mem buffer")
print("TEST 2 - save the whole screen framebuffer to a file then restore all back from the file")
led.set_rgb(0, 0, 20)
f = open(FULLSCREENFILENAME, "wb") # Save total screen framebuffer array
f.write(framebuffer)
f.close()
print("Screen framebuffer written to filename: ", FULLSCREENFILENAME)
clearrect(x0,y0,x1,y1)
f = open(FULLSCREENFILENAME, "rb")
i = 0
for y in range (0, SCREENHEIGHT, 1):
line = f.read(SCREENWIDTH)
framebuffer[i:i + SCREENWIDTH] = line
i = i + SCREENWIDTH
f.close()
display.update()
print("Screen framebuffer file read in 1 pass from filename: ", FULLSCREENFILENAME)
time.sleep(1)
# EXIT HERE
# raise SystemExit(0) # exit program (without using import sys, sys.exit("message")
print("TEST 3 - save whole screen then restore the block of screen from top bottom of damage")
led.set_rgb(20, 20, 0)
# Save total screen framebuffer array
f = open(FULLSCREENFILENAME, "wb") # Save total screen framebuffer array
f.write(framebuffer)
f.close()
print("Screen framebuffer written to filename: ", FULLSCREENFILENAME)# damage screen
clearrect(x0,y0,x1,y1) # damage screen using above co-ords
f = open(FULLSCREENFILENAME, "rb")
i = 0
for y in range (0, y0, 1): # read in lines before block
line = f.read(SCREENWIDTH) # ignore them
i = i + SCREENWIDTH # step up through framebuffer
for y in range (y0, y1+1, 1): # read in lines in the block
line = f.read(SCREENWIDTH)
framebuffer[i:i + SCREENWIDTH] = line
i = i + SCREENWIDTH
f.close()
display.update()
print("Screen framebuffer file read and updated in one pass: ", FULLSCREENFILENAME)
led.set_rgb(20, 20, 20)
print("FINISHED ***************************************************************************")