# luma.oled: driving small OLED matrices from Python with a Pillow canvas

> luma.oled is a Python 3 library that gives OLED matrix panels a Pillow-compatible drawing canvas, with drivers for a long list of SSD and SH controllers over I2C, SPI and parallel buses on Raspberry Pi and other Linux single-board computers. It is part of a wider family of luma libraries that share one API.

**rm-hull/luma.oled** — Python module to drive a SSD1306 / SSD1309 / SSD1322 / SSD1325 / SSD1327 / SSD1331 / SSD1351 / SH1106 OLED 

- Repository: https://github.com/rm-hull/luma.oled
- Website: https://luma-oled.readthedocs.io
- Stars: 880 · Forks: 173
- Language: Python
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/rm-hull-luma-oled

## Why a Python layer sits between your script and an OLED panel

An OLED matrix display does not accept an image. It accepts controller-specific register writes over a bus, and the commands differ from one controller family to the next. Left alone, every project ends up carrying its own copy of the register sequences for setting the column and page addresses, pushing pixel data and flipping the panel over to display.

luma.oled removes that layer. The README describes it as a Python 3 library that interfaces OLED matrix displays using I2C, SPI or Parallel transports on the Raspberry Pi and other Linux-based single-board computers, and it provides a Pillow-compatible drawing canvas. The consequence is that your application draws into an image object using ordinary Pillow calls, and the library handles the conversion into the protocol the panel understands.

That framing also explains who the library is for. It sits on small computers with limited resources, so the natural workloads are status boards, sensor readouts, counters and clocks rather than content work. The examples the project points to include an animated clock and a scrolling crawl, both of which are the kind of thing a Raspberry Pi in a case would sit and run for months.

## One drawing surface across a long list of SSD and SH controllers

The README lists drivers for SSD1305, SSD1306, SSD1309, SSD1315, SSD1316, SSD1322, SSD1325, SSD1327, SSD1331, SSD1351, SSD1362, SSD1363, SH1106, SH1107, WS0010 and CH1115. Panels using those controllers differ in resolution, in colour capability, and in the address window they expect, but the driver layer hides those differences behind one API.

Transport is a separate axis from the controller. The same panel family can appear on an I2C bus, an SPI bus or a parallel interface, and the choice is driven by the hardware and the wiring, not by the drawing code. A program that renders a frame does not need to change when you swap a 128x64 I2C module for a wider SPI panel that happens to use the same controller.

Worth noting is the mismatch between the repository description and the README. The description names a shorter set of controllers, SSD1306, SSD1309, SSD1322, SSD1325, SSD1327, SSD1331, SSD1351 and SH1106, while the README claims the wider list. Treat the README as the more complete statement, and confirm the specific part number against the tested-device list before you commit to it, because a controller absent from the README is a controller you would have to write yourself.

## luma.core and why the API is shared with luma.lcd and luma.led_matrix

The README explains that some core functionality was moved out into a separate repository, luma.core. That refactor is the reason luma.oled belongs to a family rather than standing alone.

Two older projects were renamed to match the same pattern. The pcd8544 project was reborn as luma.lcd, and max7219 became luma.led_matrix, and the README states plainly that the same API can be used across both projects. The practical meaning is that the drawing canvas, the Pillow-compatible surface and the transport handling are shared, while the panel-specific code differs.

For a developer this is the part that matters most. A project that renders text onto a character LCD, then later moves to a monochrome OLED, or a project that drives both a small OLED and an LED matrix in the same enclosure, can share the drawing and state-management code and change only the device class and its constructor arguments. The cost is an extra package to install and a dependency on the conventions luma.core sets, which the README does not detail but which the shared-API claim implies.

## What the canvas supports: panning, terminal printing and dithering

The README lists the feature set the library provides beyond raw pixel output. Scrolling and panning capability lets you move a larger virtual surface across a smaller physical panel, which is how the scrolling crawl example works on a display that cannot show the whole image. Terminal-style printing puts text onto the canvas the way a character terminal would, and state management keeps track of what has been drawn so that partial updates do not redraw the entire panel.

Colour and greyscale are available where the panel supports them, and dithering to monochrome handles the rest. That last one is the practical answer for anyone who designs a colour image and then discovers the panel is one bit per pixel. Rather than reducing the palette by hand, the frame is dithered, and the greys that appear on the screen are patterns of lit and unlit pixels rather than true intermediate values.

State management is the feature with the most impact on how the code is written. A display that is 128 by 64 pixels with a serial bus is expensive to refresh in full, so writing only the region that changed is the sensible default, and the library handles that bookkeeping. Code written against this API therefore differs in shape from code that redraws a Pillow image and pushes every pixel on every frame.

## Packaging: a thin setuptools project published as luma.oled

The repository is a conventional setuptools project. pyproject.toml declares the build backend and its requirements:

```toml
[build-system]
requires = ["setuptools >= 40.6.0", "wheel"]
build-backend = "setuptools.build_meta"
```

The setup.py file at the root is a shim that hands over to setuptools, with all configuration carried by setup.cfg:

```python
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import setuptools

if __name__ == "__main__":
    setuptools.setup()
```

The README badges point at a PyPI page for the project name luma.oled, which is where the distribution is published. The README itself does not spell out a pip command, and full installation instructions live in the documentation at luma-oled.readthedocs.io. The repository also ships tox.ini and pytest.ini, so the test suite is run with pytest across environments, and the source tree keeps the package under luma/ with tests/ and examples/ beside it.

One administrative point is worth flagging for anyone pinning versions. The repository has no GitHub releases, so there is no tagged release history in the GitHub interface to consult, and the CHANGES.rst file at the root is the record of what changed.

## Emulators that let you develop without the panel in front of you

Hardware-in-the-loop development for an OLED is slow in a particular way: every change to a frame means pushing bytes over a bus and then craning over to look at a 128x64 panel. The project addresses this directly. Alongside the physical drivers, luma.oled includes emulators that run in real time using pygame, and others that can take screenshots or assemble animated GIFs.

That gives three different feedback loops for different jobs. The pygame emulator is the one for interactive work, showing frames live while you tweak the layout. The screenshot mode suits a test suite that needs to assert on what was drawn. The GIF assembly mode is for producing a preview of an animation without filming a screen recording by hand.

The example source for these lives in a separate repository, luma.examples, and the README links to that repository for the animated clock and invaders examples. The important practical point is that the emulator speaks the same API as the physical driver, so a program developed against the emulator runs unchanged against real hardware once the wiring is done.

## Upgrading from the old ssd1306 package

Anyone arriving at this library from an earlier project needs one piece of history. Version 2.0.0 was released on 11 January 2017, and the README describes what changed: the GitHub project was renamed from ssd1306 to luma.oled to reflect how the codebase had grown, and core functionality moved out to the luma.core repository.

The consequence for users is that existing code importing the old ssd1306 package needs updating. The README is reassuring about the scale of that change, stating that it should be limited to altering import statements, and pointing at the upgrade documentation at luma-oled.readthedocs.io for the details. In practice this means two distinct tasks: changing the import path, and making sure the luma.core package is installed alongside luma.oled.

The licence situation is straightforward. The project is under the MIT License, with copyright held by Richard Hull and contributors from 2014 to 2026. MIT places no obligation on how you use the library beyond retaining the copyright notice and permission text in copies of the software. That is a matter of reading the licence text, not of taking legal advice, but the practical point is that embedding the library in a closed commercial product raises no stated copyleft obligation.

## What luma.oled does not cover, and where the luma siblings take over

Two limits are visible from the README alone. First, the scope is OLED matrix panels: there is no mention of e-paper, LCD or LED matrix support inside this package, so a project needing a different display technology is looking at the wrong library. Second, the absence of GitHub releases means version tracking happens on PyPI and in CHANGES.rst rather than through the tag list most people look at first.

The alternative worth understanding is not a competing project but the sibling libraries that share the same core. luma.lcd covers character and graphic LCD panels, the technology that shipped in the pcd8544 project before the rename, and luma.led_matrix covers LED matrix displays, formerly max7219. The difference in approach is the transport and the driver layer, not the drawing model: the Pillow-compatible canvas, the panning, the state management and the emulator pattern are common across the family.

That reframes the decision. If your output device is a monochrome OLED, luma.oled is the member of the family that speaks its protocol. If you might later switch to an LCD or a matrix, writing against luma.core from the start keeps the drawing code intact, at the cost of depending on a convention the README does not fully document.

## Conclusion

Adopt luma.oled when your project needs to push text, sensor values or animation onto a small OLED panel from a Raspberry Pi or a similar Linux board, and you want the same drawing code to work across panel types. Do not reach for it if your target is an e-paper display, or if you need a library that already ships tagged releases on GitHub, because this repository publishes none. Before writing code, confirm your exact controller is on the tested-device list in the project wiki and that luma.core is installed alongside luma.oled, then check that the panel's bus mode matches the transport you select.

## FAQ

### How do I install luma.oled on a Raspberry Pi?

The package is published on PyPI under the name luma.oled, and the README states that full installation instructions are on luma-oled.readthedocs.io. Because core functionality lives in a separate repository, luma.core has to be installed alongside it.

### Which OLED controllers can luma.oled drive?

The README lists drivers for SSD1305, SSD1306, SSD1309, SSD1315, SSD1316, SSD1322, SSD1325, SSD1327, SSD1331, SSD1351, SSD1362, SSD1363, SH1106, SH1107, WS0010 and CH1115, over I2C, SPI or parallel transports. A list of tested devices is kept in the project wiki.

### Can I write luma.oled code without a physical display attached?

Yes. The project includes emulators that run in real time using pygame, and others that take screenshots or assemble animated GIFs. The example source for the clock and invaders animations lives in the separate luma.examples repository.

## Sources

- [Issues](https://github.com/rm-hull/luma.oled/issues)
- [License: MIT](https://github.com/rm-hull/luma.oled/blob/main/LICENSE)
- [Project website](https://luma-oled.readthedocs.io)
- [README](https://github.com/rm-hull/luma.oled/blob/main/README.md)
- [rm-hull/luma.oled on GitHub](https://github.com/rm-hull/luma.oled)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/rm-hull-luma-oled
