The display
The brick’s LCD is 100×64 pixels. You can read what it shows and draw your own text and images on it.
Screenshots
brick.screenshot() captures the screen:
nxt_rs::doc_example(|brick| async move {
let screen = brick.screenshot().await?;
print!("{}", screen.to_text()); // half-block characters in the terminal
std::fs::write(std::env::temp_dir().join("nxt.pbm"), screen.to_pbm())?; // a PBM image
let dark = screen.pixel(10, 20); // single pixels, (0, 0) is top left
Ok(()) });
- PBM is a simple image format that most image tools can open. Convert
it with e.g. ImageMagick:
magick nxt.pbm nxt.png. screen.bytes()returns the raw 800-byte buffer: 8 rows of 100 bytes, each byte a vertical strip of 8 pixels with the least significant bit on top.
Text
brick.show_text(..) clears the screen and prints text from the top left
corner. The built-in 5×7 pixel font sits on a 6×8 grid, so the screen fits
8 lines of 16 characters. \n starts a new line:
nxt_rs::doc_example(|brick| async move {
brick.show_text("Hello,\nNXT!").await?;
Ok(()) });
Long lines are cut off, not wrapped. Characters outside printable ASCII are
drawn as ?.
Composing a screen
For more control, build a Screen and send it with brick.set_screen(..).
draw_text places text at any pixel position; each character cell is drawn
opaque, so text stays readable on top of a picture:
nxt_rs::doc_example(|brick| async move {
use nxt_rs::display::{CHAR_HEIGHT, Image, Screen};
let mut screen = Screen::blank();
screen.draw_text(0, 0, "Distance");
screen.draw_text(0, CHAR_HEIGHT, &format!("{:>3} cm", 42));
screen.draw_image(60, 0, &Image::from_fn(40, 40, |x, y| x == y || x + y == 39));
screen.set_pixel(99, 63, true);
brick.set_screen(&screen).await?;
Ok(()) });
Images
An Image is a black and white picture of any size. Draw it with
screen.draw_image(x, y, &image), or show it alone with
brick.show_image(&image). Pixels past the edge of the screen are cut off.
Create one:
- From a PBM file with
Image::from_pbm(..), binary (P4) or plain (P1). Turn any picture into one with ImageMagick:magick logo.png -resize 100x64 -monochrome logo.pbm. - From grayscale pixels with
Image::from_luma(width, height, &pixels), one byte per pixel, 0 black and 255 white. Pixels darker than 50% gray become dark. For photos,Image::from_luma_dithered(..)keeps shades of gray visible as patterns of dots. - From a function with
Image::from_fn(width, height, |x, y| ..).
nxt_rs::doc_example(|brick| async move {
use nxt_rs::display::Image;
let logo = Image::from_pbm(b"P1\n5 5\n00100 01110 11111 01110 00100")?;
brick.show_image(&logo).await?;
Ok(()) });
The library decodes no other formats. To show a PNG or JPEG, decode, scale
and convert it with an image library such as the
image crate, then pass the grayscale
pixels on:
let gray = image::open("photo.jpg")?.resize(100, 64, image::imageops::FilterType::Lanczos3).to_luma8();
let photo = Image::from_luma_dithered(gray.width() as usize, gray.height() as usize, gray.as_raw())?;
brick.show_image(&photo).await?;
How it works
Screenshots and drawing read and write the screen buffer in the display module’s I/O map, in about 15 requests. That takes a moment over USB and a second or two over Bluetooth. The layout matches LEGO’s standard firmware. Custom firmware may lay out memory differently.
The brick shows whatever is in the buffer, so a drawing stays until something else draws over it: a running program, or the brick’s menu when you press a button. While the menu is open it also redraws its status bar (the top 8 rows) from time to time. To keep a drawing up, redraw it periodically or keep text out of the top line.