RasterExRatatui
Render ExRatatui apps on pixel displays such as e-ink panels, with helpers for Linux framebuffers.
A terminal paints glyphs for us. A panel with nothing but pixels does not, so something has to turn every cell (a symbol, a foreground, a background) into pixels, and blit the bitmaps that Viewport3D and Image render. RasterExRatatui is that something. It sits on top of an ExRatatui.CellSession, keeps the cell grid and the pixel regions of the current frame, and hands the device only the rectangles that changed, already packed in the panel's pixel format.
ExRatatui app ──> ExRatatui.Server ──> CellSession diff (cells + regions)
│
RasterExRatatui.Raster (font × palette × pixel format × scale)
│
[%Patch{x, y, width, height, data}]
│
Surface.push/2 ──> the panel
Features
- A surface is one module —
use RasterExRatatui.Surface, return the panel geometry frominit/1, write pixels inpush/2. The surface process starts the app, folds diffs, rasterises, and forwards input. - Patches, not frames — only changed cells (and changed pixel regions) are rasterised and pushed;
Raster.frame/1renders a full buffer for panels that want one. - Pixel regions —
Viewport3DandImagearrive as RGB bitmaps and are scaled onto their cell rect at the panel's native resolution. - Pixel formats —
Mono(1-bit panels: tone rules for cells, Bayer dither for regions),RGB565, andXRGB8888, with a palette for named, indexed, and RGB colours. - Fonts — a built-in 6×8 bitmap font with box drawing, blocks, and braille, an integer
scale:for large panels, and aFontbehaviour to bring another. - Device helpers —
Framebufferfor Linux fbdev (geometry from sysfs, stride-aware writes) andInput.Evdevto turn keyboard events intoExRatatui.Event.Keystructs. - Pure core —
Raster,Grid, fonts, and formats are plain functions, usable from any process that already owns its device.
Quick start
The app needs no change: it is a plain use ExRatatui.App (or ExRatatui.run/2) that renders widgets exactly as it would in a terminal, so build and try it there first. Putting it on a panel is then one module, the surface, that answers three questions about the display: how big it is, how its pixels are packed, and how bytes reach it.
Pick the format and scale.
Monofor 1-bit panels,RGB565orXRGB8888for colour.scale:magnifies the built-in 6×8 font so the grid stays readable on a large panel: a 720×1280 display at scale 3 gives 40×53 cells.Write the surface.
init/1opens the device and returns its geometry,push/2writes the rectangles that changed. On a Linux framebuffer (a Raspberry Pi with a display) the helpers do both:defmodule MyDevice.Surface douse RasterExRatatui.Surface, app: MyDevice.App, scale: 3alias RasterExRatatui.Framebuffer@impl truedef init(_opts) do{:ok, fb} = Framebuffer.open("fb0"){:ok, format} = Framebuffer.format_for(fb.info){:ok, [size: {fb.info.width, fb.info.height}, format: format], fb}end@impl truedef push(patches, fb) do:ok = Framebuffer.write(fb, patches)fbendendA panel the kernel does not expose as a framebuffer (an SPI LCD, an e-ink controller) writes each patch with its own driver in
push/2instead.Supervise it and wire input. Add
MyDevice.Surfaceto the supervision tree and the app is on the display. Whatever reads the hardware (a keyboard, GPIO buttons) turns its events intoExRatatui.Eventstructs and hands them over withRasterExRatatui.Surface.send_event/2;RasterExRatatui.Input.Evdevdoes the translation for evdev keyboards.Test on the host. A surface whose
push/2sends patches to the test process drives the real app without a device, andRasterExRatatui.Raster.frame/1shows exactly what the panel would.
Building a Surface walks through each step, including panels that only take whole frames, slow refreshes, crashes, and resizing. Linux Framebuffers covers /dev/fb0, keeping the kernel console off the display, and keyboards. A device already driven from its own process (the name badge is one) can skip the surface and fold diffs with RasterExRatatui.Raster directly.
Examples
- Headless snapshot — the whole pipeline in one file, with no device or terminal: a dashboard with a
Viewport3Dcube rasterised as a colour frame (XRGB8888, scale 2) and as a 1-bit e-ink frame (Mono), written as PPM and PGM images.mix run examples/headless/snapshot.exsfrom a checkout. - Goatmire name badge — the consumer the library was extracted from: a 400×300 1-bit e-ink panel on Nerves, driven from the badge's own screen process with the pure core (
Raster.apply/2andPatch.blit/4), with a crash frame and two GPIO buttons as key events. A branch of the name_badge fork. - Raspberry Pi 4 with the Touch Display 2 — a Linux framebuffer surface (
/dev/fb0, a USB keyboard throughinput_event) as a Nerves project underexamples/. In progress: it lands after its first device run, and Linux Framebuffers already shows the surface it uses.
The examples catalog says what to look at in each.
Guides
| Guide | Description |
|---|---|
| Building a Surface | The contract: geometry, push, input, crashes, testing, and the name badge as a worked example |
| Fonts | The glyph layout, the built-in 6×8 font, scale, and bringing a font |
| Pixel Formats | Mono tone rules and dithering, colour palettes, writing a format |
| Linux Framebuffers | Framebuffer and Input.Evdev: device geometry, writes, the kernel console, and keyboards |
| Telemetry | Rasterisation, push, and input events with a Telemetry.Metrics example |
Ecosystem
- ex_ratatui — The core terminal UI library this builds on.
- phoenix_ex_ratatui — Run TUIs in the browser within Phoenix LiveView.
- kino_ex_ratatui — Run TUIs inside Livebook notebooks.
Installation
Add raster_ex_ratatui to the dependencies in mix.exs:
def deps do
[
{:raster_ex_ratatui, "~> 0.1"}
]
end
Prerequisites
- Elixir 1.17+
- ex_ratatui 0.14 or later (pixel regions:
CellSession.new/3withfont_size:)
Contributing
See CONTRIBUTING.md for development setup and guidelines.
License
MIT — see LICENSE.