Blitting Buffers
Here is the trick that makes complex faces fast. Instead of redrawing an eye from scratch every single frame, you draw it once into a small off-screen buffer and then stamp that finished picture onto the display wherever you need it. That stamping operation is called a blit — short for "block image transfer."
1 | |
The source_buffer is another FrameBuffer object, and x and y say where its top-left corner should land on the display. The optional key marks one color as transparent, which we will come back to below.
Making an Off-Screen Buffer
A FrameBuffer is just a block of memory plus the drawing commands you already know. You create one by handing it a bytearray big enough to hold the pixels, along with the width, height, and pixel format:
1 2 3 4 | |
That // 8 is doing real work. In a monochrome buffer each pixel is one bit, and a byte holds 8 bits, so a 32 by 24 buffer needs 32 * 24 / 8 = 96 bytes. MONO_HLSB names the layout — monochrome, packed horizontally, most significant bit on the left.
The important part is what comes next: eye now responds to fill(), ellipse(), line(), and every other drawing command, exactly like the display does. The only difference is that nothing you draw into it is visible until you blit it.
The Display Is Just a Buffer That Happens to Be Visible
Your oled object is a frame buffer too — it simply has a screen attached. Once that clicks, blitting stops feeling exotic. You're copying one rectangle of pixels into another.
Sample Program Code
This program builds one eye in a 32 by 24 buffer, stamps it twice to make a matching pair, then blits it two more times over a striped background — once normally, and once with transparency turned on.
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 | |
Here's what that program draws on the display:

Reading the Output
The two eyes across the top are identical, and they cost one ellipse() pair to build plus two cheap copies to place. Change the buffer once and both eyes change together — that is the whole reason to work this way.
The bottom half is where transparency shows itself. Both shapes came from the same buffer, but they behave completely differently against the stripes:
| Call | What lands on the display |
|---|---|
blit(eye, 12, 36) |
All 768 pixels, black ones included — the stripes get wiped out inside a 32 by 24 rectangle |
blit(eye, 84, 36, 0) |
Only the white pixels — every black pixel is skipped, so the stripes survive |
Look at the pupil on the right-hand eye. It was drawn in black, so with key=0 it became transparent too, and the stripes run straight through it. That is not a bug — it is exactly what you asked for.
Transparency Applies to Every Pixel of That Color
A key doesn't know the difference between a background you wanted to hide and a pupil you wanted to keep. If a detail vanishes after you add a key, check whether that detail was drawn in the key color.
Typing a Sprite by Hand
You do not have to draw a sprite with shape commands. For small icons it is often easier to type the bits directly, one row per line, using Python's binary notation:
1 2 3 4 5 6 7 8 9 10 11 | |
Each 0b value is one row of 8 pixels, and every 1 is a lit dot. Squint at those eight lines and you can see the plus sign right there in the source code — which makes hand-typed sprites surprisingly easy to edit.
You Now Have Every Drawing Tool the FrameBuf Offers
Pixels, lines, rectangles, ellipses, polygons, text, scrolling, and now blitting. Everything from here on is combining these eight ideas into faces worth looking at. Every pixel tells a story!
Challenge
- Build a second buffer holding a closed eye, and blink by blitting whichever one matches the current state.
- Draw a mouth into its own buffer and blit it at several vertical positions to make the face "talk."
- Type an 8 by 8 sprite of your own by hand and blit it across the screen in a row.
- Measure how long it takes to draw 10 eyes with
ellipse()versus blitting the same eye 10 times. Which wins, and why?